##// END OF EJS Templates
merged beta into default branch
marcink -
r2165:dc2584ba merge default
parent child Browse files
Show More
@@ -0,0 +1,30 b''
1 .. _debugging:
2
3 ===================
4 Debugging RhodeCode
5 ===================
6
7 If you encountered problems with RhodeCode here are some instructions how to
8 possibly debug them.
9
10 ** First make sure you're using the latest version available.**
11
12 enable detailed debug
13 ---------------------
14
15 RhodeCode uses standard python logging modules to log it's output.
16 By default only loggers with INFO level are displayed. To enable full output
17 change `level = DEBUG` for all logging handlers in currently used .ini file.
18 This change will allow to see much more detailed output in the logfile or
19 console. This generally helps a lot to track issues.
20
21
22 enable interactive debug mode
23 -----------------------------
24
25 To enable interactive debug mode simply comment out `set debug = false` in
26 .ini file, this will trigger and interactive debugger each time there an
27 error in browser, or send a http link if error occured in the backend. This
28 is a great tool for fast debugging as you get a handy python console right
29 in the web view. ** NEVER ENABLE THIS ON PRODUCTION ** the interactive console
30 can be a serious security threat to you system.
@@ -0,0 +1,70 b''
1 #!/bin/bash
2 ###########################################
3 #### THIS IS AN ARCH LINUX RC.D SCRIPT ####
4 ###########################################
5
6 . /etc/rc.conf
7 . /etc/rc.d/functions
8
9 DAEMON=rhodecode
10 APP_HOMEDIR="/srv"
11 APP_PATH="$APP_HOMEDIR/$DAEMON"
12 CONF_NAME="production.ini"
13 LOG_FILE="/var/log/$DAEMON.log"
14 PID_FILE="/run/daemons/$DAEMON"
15 APPL=/usr/bin/paster
16 RUN_AS="*****"
17
18 ARGS="serve --daemon \
19 --user=$RUN_AS \
20 --group=$RUN_AS \
21 --pid-file=$PID_FILE \
22 --log-file=$LOG_FILE \
23 $APP_PATH/$CONF_NAME"
24
25 [ -r /etc/conf.d/$DAEMON ] && . /etc/conf.d/$DAEMON
26
27 if [[ -r $PID_FILE ]]; then
28 read -r PID < "$PID_FILE"
29 if [[ $PID && ! -d /proc/$PID ]]; then
30 unset PID
31 rm_daemon $DAEMON
32 fi
33 fi
34
35 case "$1" in
36 start)
37 stat_busy "Starting $DAEMON"
38 export HOME=$APP_PATH
39 [ -z "$PID" ] && $APPL $ARGS &>/dev/null
40 if [ $? = 0 ]; then
41 add_daemon $DAEMON
42 stat_done
43 else
44 stat_fail
45 exit 1
46 fi
47 ;;
48 stop)
49 stat_busy "Stopping $DAEMON"
50 [ -n "$PID" ] && kill $PID &>/dev/null
51 if [ $? = 0 ]; then
52 rm_daemon $DAEMON
53 stat_done
54 else
55 stat_fail
56 exit 1
57 fi
58 ;;
59 restart)
60 $0 stop
61 sleep 1
62 $0 start
63 ;;
64 status)
65 stat_busy "Checking $name status";
66 ck_status $name
67 ;;
68 *)
69 echo "usage: $0 {start|stop|restart|status}"
70 esac No newline at end of file
@@ -0,0 +1,58 b''
1 # -*- coding: utf-8 -*-
2 """
3 package.rhodecode.config.conf
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Various config settings for RhodeCode
7
8 :created_on: Mar 7, 2012
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: <name>, see LICENSE_FILE for more details.
12 """
13 from rhodecode import EXTENSIONS
14
15 from rhodecode.lib.utils2 import __get_lem
16
17
18 # language map is also used by whoosh indexer, which for those specified
19 # extensions will index it's content
20 LANGUAGES_EXTENSIONS_MAP = __get_lem()
21
22 #==============================================================================
23 # WHOOSH INDEX EXTENSIONS
24 #==============================================================================
25 # EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH
26 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
27
28 # list of readme files to search in file tree and display in summary
29 # attached weights defines the search order lower is first
30 ALL_READMES = [
31 ('readme', 0), ('README', 0), ('Readme', 0),
32 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
33 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
34 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
35 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
36 ]
37
38 # extension together with weights to search lower is first
39 RST_EXTS = [
40 ('', 0), ('.rst', 1), ('.rest', 1),
41 ('.RST', 2), ('.REST', 2),
42 ('.txt', 3), ('.TXT', 3)
43 ]
44
45 MARKDOWN_EXTS = [
46 ('.md', 1), ('.MD', 1),
47 ('.mkdn', 2), ('.MKDN', 2),
48 ('.mdown', 3), ('.MDOWN', 3),
49 ('.markdown', 4), ('.MARKDOWN', 4)
50 ]
51
52 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
53
54 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
55
56 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
57
58 DATE_FORMAT = "%Y-%m-%d"
@@ -0,0 +1,84 b''
1 # Additional mappings that are not present in the pygments lexers
2 # used for building stats
3 # format is {'ext':'Name'} eg. {'py':'Python'}
4 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
5 # build by pygments
6 EXTRA_MAPPINGS = {}
7
8 #==============================================================================
9 # WHOOSH INDEX EXTENSIONS
10 #==============================================================================
11 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
12 # To set your own just add to this list extensions to index with content
13 INDEX_EXTENSIONS = []
14
15 # additional extensions for indexing besides the default from pygments
16 # those get's added to INDEX_EXTENSIONS
17 EXTRA_INDEX_EXTENSIONS = []
18
19
20 #==============================================================================
21 # POST CREATE REPOSITORY HOOK
22 #==============================================================================
23 # this function will be executed after each repository is created
24 def _crhook(*args, **kwargs):
25 """
26 Post create repository HOOK
27 kwargs available:
28 :param repo_name:
29 :param repo_type:
30 :param description:
31 :param private:
32 :param created_on:
33 :param enable_downloads:
34 :param repo_id:
35 :param user_id:
36 :param enable_statistics:
37 :param clone_uri:
38 :param fork_id:
39 :param group_id:
40 :param created_by:
41 """
42 return 0
43 CREATE_REPO_HOOK = _crhook
44
45
46 #==============================================================================
47 # POST PUSH HOOK
48 #==============================================================================
49
50 # this function will be executed after each push it's runned after the build-in
51 # hook that rhodecode uses for logging pushes
52 def _pushhook(*args, **kwargs):
53 """
54 Post push hook
55 kwargs available:
56
57 :param username: name of user who pushed
58 :param ip: ip of who pushed
59 :param action: pull
60 :param repository: repository name
61 :param pushed_revs: generator of pushed revisions
62 """
63 return 0
64 PUSH_HOOK = _pushhook
65
66
67 #==============================================================================
68 # POST PULL HOOK
69 #==============================================================================
70
71 # this function will be executed after each push it's runned after the build-in
72 # hook that rhodecode uses for logging pushes
73 def _pullhook(*args, **kwargs):
74 """
75 Post pull hook
76 kwargs available::
77
78 :param username: name of user who pulled
79 :param ip: ip of who pushed
80 :param action: pull
81 :param repository: repository name
82 """
83 return 0
84 PULL_HOOK = _pullhook
@@ -0,0 +1,79 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.config.rcextensions.make_rcextensions
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Whoosh indexing module for RhodeCode
7
8 :created_on: Mar 6, 2012
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 import os
26 import sys
27 import pkg_resources
28 import traceback
29 import logging
30 from os.path import dirname as dn, join as jn
31
32 #to get the rhodecode import
33 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
34
35 from rhodecode.lib.utils import BasePasterCommand, Command, ask_ok
36
37 log = logging.getLogger(__name__)
38
39
40 class MakeRcExt(BasePasterCommand):
41
42 max_args = 1
43 min_args = 1
44
45 usage = "CONFIG_FILE"
46 summary = "Creates additional extensions for rhodecode"
47 group_name = "RhodeCode"
48 takes_config_file = -1
49 parser = Command.standard_parser(verbose=True)
50
51 def command(self):
52 logging.config.fileConfig(self.path_to_ini_file)
53 from pylons import config
54
55 def _make_file(ext_file):
56 bdir = os.path.split(ext_file)[0]
57 if not os.path.isdir(bdir):
58 os.makedirs(bdir)
59 with open(ext_file, 'wb') as f:
60 f.write(tmpl)
61 log.info('Writen new extensions file to %s' % ext_file)
62
63 here = config['here']
64 tmpl = pkg_resources.resource_string(
65 'rhodecode', jn('config', 'rcextensions', '__init__.py')
66 )
67 ext_file = jn(here, 'rcextensions', '__init__.py')
68 if os.path.exists(ext_file):
69 msg = ('Extension file already exists, do you want '
70 'to overwrite it ? [y/n]')
71 if ask_ok(msg):
72 _make_file(ext_file)
73 else:
74 log.info('nothing done...')
75 else:
76 _make_file(ext_file)
77
78 def update_parser(self):
79 pass
@@ -0,0 +1,405 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
5
6 Some simple helper functions
7
8 :created_on: Jan 5, 2011
9 :author: marcink
10 :copyright: (C) 2011-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 import re
27 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28
29
30 def __get_lem():
31 """
32 Get language extension map based on what's inside pygments lexers
33 """
34 from pygments import lexers
35 from string import lower
36 from collections import defaultdict
37
38 d = defaultdict(lambda: [])
39
40 def __clean(s):
41 s = s.lstrip('*')
42 s = s.lstrip('.')
43
44 if s.find('[') != -1:
45 exts = []
46 start, stop = s.find('['), s.find(']')
47
48 for suffix in s[start + 1:stop]:
49 exts.append(s[:s.find('[')] + suffix)
50 return map(lower, exts)
51 else:
52 return map(lower, [s])
53
54 for lx, t in sorted(lexers.LEXERS.items()):
55 m = map(__clean, t[-2])
56 if m:
57 m = reduce(lambda x, y: x + y, m)
58 for ext in m:
59 desc = lx.replace('Lexer', '')
60 d[ext].append(desc)
61
62 return dict(d)
63
64 def str2bool(_str):
65 """
66 returs True/False value from given string, it tries to translate the
67 string into boolean
68
69 :param _str: string value to translate into boolean
70 :rtype: boolean
71 :returns: boolean from given string
72 """
73 if _str is None:
74 return False
75 if _str in (True, False):
76 return _str
77 _str = str(_str).strip().lower()
78 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
79
80
81 def convert_line_endings(line, mode):
82 """
83 Converts a given line "line end" accordingly to given mode
84
85 Available modes are::
86 0 - Unix
87 1 - Mac
88 2 - DOS
89
90 :param line: given line to convert
91 :param mode: mode to convert to
92 :rtype: str
93 :return: converted line according to mode
94 """
95 from string import replace
96
97 if mode == 0:
98 line = replace(line, '\r\n', '\n')
99 line = replace(line, '\r', '\n')
100 elif mode == 1:
101 line = replace(line, '\r\n', '\r')
102 line = replace(line, '\n', '\r')
103 elif mode == 2:
104 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
105 return line
106
107
108 def detect_mode(line, default):
109 """
110 Detects line break for given line, if line break couldn't be found
111 given default value is returned
112
113 :param line: str line
114 :param default: default
115 :rtype: int
116 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
117 """
118 if line.endswith('\r\n'):
119 return 2
120 elif line.endswith('\n'):
121 return 0
122 elif line.endswith('\r'):
123 return 1
124 else:
125 return default
126
127
128 def generate_api_key(username, salt=None):
129 """
130 Generates unique API key for given username, if salt is not given
131 it'll be generated from some random string
132
133 :param username: username as string
134 :param salt: salt to hash generate KEY
135 :rtype: str
136 :returns: sha1 hash from username+salt
137 """
138 from tempfile import _RandomNameSequence
139 import hashlib
140
141 if salt is None:
142 salt = _RandomNameSequence().next()
143
144 return hashlib.sha1(username + salt).hexdigest()
145
146
147 def safe_unicode(str_, from_encoding=None):
148 """
149 safe unicode function. Does few trick to turn str_ into unicode
150
151 In case of UnicodeDecode error we try to return it with encoding detected
152 by chardet library if it fails fallback to unicode with errors replaced
153
154 :param str_: string to decode
155 :rtype: unicode
156 :returns: unicode object
157 """
158 if isinstance(str_, unicode):
159 return str_
160
161 if not from_encoding:
162 import rhodecode
163 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
164 from_encoding = DEFAULT_ENCODING
165
166 try:
167 return unicode(str_)
168 except UnicodeDecodeError:
169 pass
170
171 try:
172 return unicode(str_, from_encoding)
173 except UnicodeDecodeError:
174 pass
175
176 try:
177 import chardet
178 encoding = chardet.detect(str_)['encoding']
179 if encoding is None:
180 raise Exception()
181 return str_.decode(encoding)
182 except (ImportError, UnicodeDecodeError, Exception):
183 return unicode(str_, from_encoding, 'replace')
184
185
186 def safe_str(unicode_, to_encoding=None):
187 """
188 safe str function. Does few trick to turn unicode_ into string
189
190 In case of UnicodeEncodeError we try to return it with encoding detected
191 by chardet library if it fails fallback to string with errors replaced
192
193 :param unicode_: unicode to encode
194 :rtype: str
195 :returns: str object
196 """
197
198 # if it's not basestr cast to str
199 if not isinstance(unicode_, basestring):
200 return str(unicode_)
201
202 if isinstance(unicode_, str):
203 return unicode_
204
205 if not to_encoding:
206 import rhodecode
207 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
208 to_encoding = DEFAULT_ENCODING
209
210 try:
211 return unicode_.encode(to_encoding)
212 except UnicodeEncodeError:
213 pass
214
215 try:
216 import chardet
217 encoding = chardet.detect(unicode_)['encoding']
218 print encoding
219 if encoding is None:
220 raise UnicodeEncodeError()
221
222 return unicode_.encode(encoding)
223 except (ImportError, UnicodeEncodeError):
224 return unicode_.encode(to_encoding, 'replace')
225
226 return safe_str
227
228
229 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
230 """
231 Custom engine_from_config functions that makes sure we use NullPool for
232 file based sqlite databases. This prevents errors on sqlite. This only
233 applies to sqlalchemy versions < 0.7.0
234
235 """
236 import sqlalchemy
237 from sqlalchemy import engine_from_config as efc
238 import logging
239
240 if int(sqlalchemy.__version__.split('.')[1]) < 7:
241
242 # This solution should work for sqlalchemy < 0.7.0, and should use
243 # proxy=TimerProxy() for execution time profiling
244
245 from sqlalchemy.pool import NullPool
246 url = configuration[prefix + 'url']
247
248 if url.startswith('sqlite'):
249 kwargs.update({'poolclass': NullPool})
250 return efc(configuration, prefix, **kwargs)
251 else:
252 import time
253 from sqlalchemy import event
254 from sqlalchemy.engine import Engine
255
256 log = logging.getLogger('sqlalchemy.engine')
257 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
258 engine = efc(configuration, prefix, **kwargs)
259
260 def color_sql(sql):
261 COLOR_SEQ = "\033[1;%dm"
262 COLOR_SQL = YELLOW
263 normal = '\x1b[0m'
264 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
265
266 if configuration['debug']:
267 #attach events only for debug configuration
268
269 def before_cursor_execute(conn, cursor, statement,
270 parameters, context, executemany):
271 context._query_start_time = time.time()
272 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
273
274
275 def after_cursor_execute(conn, cursor, statement,
276 parameters, context, executemany):
277 total = time.time() - context._query_start_time
278 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
279
280 event.listen(engine, "before_cursor_execute",
281 before_cursor_execute)
282 event.listen(engine, "after_cursor_execute",
283 after_cursor_execute)
284
285 return engine
286
287
288 def age(curdate):
289 """
290 turns a datetime into an age string.
291
292 :param curdate: datetime object
293 :rtype: unicode
294 :returns: unicode words describing age
295 """
296
297 from datetime import datetime
298 from webhelpers.date import time_ago_in_words
299
300 _ = lambda s: s
301
302 if not curdate:
303 return ''
304
305 agescales = [(_(u"year"), 3600 * 24 * 365),
306 (_(u"month"), 3600 * 24 * 30),
307 (_(u"day"), 3600 * 24),
308 (_(u"hour"), 3600),
309 (_(u"minute"), 60),
310 (_(u"second"), 1), ]
311
312 age = datetime.now() - curdate
313 age_seconds = (age.days * agescales[2][1]) + age.seconds
314 pos = 1
315 for scale in agescales:
316 if scale[1] <= age_seconds:
317 if pos == 6:
318 pos = 5
319 return '%s %s' % (time_ago_in_words(curdate,
320 agescales[pos][0]), _('ago'))
321 pos += 1
322
323 return _(u'just now')
324
325
326 def uri_filter(uri):
327 """
328 Removes user:password from given url string
329
330 :param uri:
331 :rtype: unicode
332 :returns: filtered list of strings
333 """
334 if not uri:
335 return ''
336
337 proto = ''
338
339 for pat in ('https://', 'http://'):
340 if uri.startswith(pat):
341 uri = uri[len(pat):]
342 proto = pat
343 break
344
345 # remove passwords and username
346 uri = uri[uri.find('@') + 1:]
347
348 # get the port
349 cred_pos = uri.find(':')
350 if cred_pos == -1:
351 host, port = uri, None
352 else:
353 host, port = uri[:cred_pos], uri[cred_pos + 1:]
354
355 return filter(None, [proto, host, port])
356
357
358 def credentials_filter(uri):
359 """
360 Returns a url with removed credentials
361
362 :param uri:
363 """
364
365 uri = uri_filter(uri)
366 #check if we have port
367 if len(uri) > 2 and uri[2]:
368 uri[2] = ':' + uri[2]
369
370 return ''.join(uri)
371
372
373 def get_changeset_safe(repo, rev):
374 """
375 Safe version of get_changeset if this changeset doesn't exists for a
376 repo it returns a Dummy one instead
377
378 :param repo:
379 :param rev:
380 """
381 from rhodecode.lib.vcs.backends.base import BaseRepository
382 from rhodecode.lib.vcs.exceptions import RepositoryError
383 if not isinstance(repo, BaseRepository):
384 raise Exception('You must pass an Repository '
385 'object as first argument got %s', type(repo))
386
387 try:
388 cs = repo.get_changeset(rev)
389 except RepositoryError:
390 from rhodecode.lib.utils import EmptyChangeset
391 cs = EmptyChangeset(requested_revision=rev)
392 return cs
393
394
395 def extract_mentioned_users(s):
396 """
397 Returns unique usernames from given string s that have @mention
398
399 :param s: string to get mentions
400 """
401 usrs = {}
402 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
403 usrs[username] = username
404
405 return sorted(usrs.keys())
@@ -6,6 +6,7 b' syntax: glob'
6 *.egg
6 *.egg
7
7
8 syntax: regexp
8 syntax: regexp
9 ^rcextensions
9 ^build
10 ^build
10 ^docs/build/
11 ^docs/build/
11 ^docs/_build/
12 ^docs/_build/
@@ -15,10 +15,11 b' RhodeCode is similar in some respects to'
15 however RhodeCode can be run as standalone hosted application on your own server.
15 however RhodeCode can be run as standalone hosted application on your own server.
16 It is open source and donation ware and focuses more on providing a customized,
16 It is open source and donation ware and focuses more on providing a customized,
17 self administered interface for Mercurial_ and GIT_ repositories.
17 self administered interface for Mercurial_ and GIT_ repositories.
18 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
18 RhodeCode works on *nix systems and Windows it is powered by a vcs_ library
19 handle multiple different version control systems.
19 that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple
20 different version control systems.
20
21
21 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
22 RhodeCode uses `PEP386 versioning http://www.python.org/dev/peps/pep-0386/`_
22
23
23 Installation
24 Installation
24 ------------
25 ------------
@@ -99,7 +100,7 b' RhodeCode Features'
99 - Intelligent cache with invalidation after push or project change, provides
100 - Intelligent cache with invalidation after push or project change, provides
100 high performance and always up to date data.
101 high performance and always up to date data.
101 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
102 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
102 - Async tasks for speed and performance using celery_ (works without them too)
103 - Optional async tasks for speed and performance using celery_
103 - Backup scripts can do backup of whole app and send it over scp to desired
104 - Backup scripts can do backup of whole app and send it over scp to desired
104 location
105 location
105 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
106 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
@@ -93,6 +93,11 b' issue_prefix = #'
93 ## all running rhodecode instances. Leave empty if you don't use it
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
94 instance_id =
95
95
96 ## alternative return HTTP header for failed authentication. Default HTTP
97 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
98 ## handling that. Set this variable to 403 to return HTTPForbidden
99 auth_ret_code =
100
96 ####################################
101 ####################################
97 ### CELERY CONFIG ####
102 ### CELERY CONFIG ####
98 ####################################
103 ####################################
@@ -171,6 +176,7 b' beaker.cache.sql_cache_long.key_length ='
171
176
172 beaker.session.type = file
177 beaker.session.type = file
173 beaker.session.key = rhodecode
178 beaker.session.key = rhodecode
179 # secure cookie requires AES python libraries
174 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
180 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
175 #beaker.session.validate_key = 9712sds2212c--zxc123
181 #beaker.session.validate_key = 9712sds2212c--zxc123
176 beaker.session.timeout = 36000
182 beaker.session.timeout = 36000
@@ -207,13 +213,13 b' logview.pylons.util = #eee'
207 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
213 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
208 sqlalchemy.db1.echo = false
214 sqlalchemy.db1.echo = false
209 sqlalchemy.db1.pool_recycle = 3600
215 sqlalchemy.db1.pool_recycle = 3600
210 sqlalchemy.convert_unicode = true
216 sqlalchemy.db1.convert_unicode = true
211
217
212 ################################
218 ################################
213 ### LOGGING CONFIGURATION ####
219 ### LOGGING CONFIGURATION ####
214 ################################
220 ################################
215 [loggers]
221 [loggers]
216 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
222 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
217
223
218 [handlers]
224 [handlers]
219 keys = console, console_sql
225 keys = console, console_sql
@@ -259,6 +265,12 b' handlers = console_sql'
259 qualname = sqlalchemy.engine
265 qualname = sqlalchemy.engine
260 propagate = 0
266 propagate = 0
261
267
268 [logger_whoosh_indexer]
269 level = DEBUG
270 handlers =
271 qualname = whoosh_indexer
272 propagate = 1
273
262 ##############
274 ##############
263 ## HANDLERS ##
275 ## HANDLERS ##
264 ##############
276 ##############
@@ -27,7 +27,7 b' API ACCESS'
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:<id>,
30 "id:"<id>",
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
@@ -50,9 +50,9 b' Simply provide'
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>,
53 "id":<id>, # matching id sent by request
54 "result": "<result>",
54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 "error": null
55 "error": "null"|<error_message> # JSON formatted error (if any)
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
@@ -72,6 +72,7 b' belonging to user with admin rights'
72
72
73 INPUT::
73 INPUT::
74
74
75 id : <id_for_response>
75 api_key : "<api_key>"
76 api_key : "<api_key>"
76 method : "pull"
77 method : "pull"
77 args : {
78 args : {
@@ -94,6 +95,7 b' rights.'
94
95
95 INPUT::
96 INPUT::
96
97
98 id : <id_for_response>
97 api_key : "<api_key>"
99 api_key : "<api_key>"
98 method : "get_user"
100 method : "get_user"
99 args : {
101 args : {
@@ -111,7 +113,15 b' OUTPUT::'
111 "email" : "<email>",
113 "email" : "<email>",
112 "active" : "<bool>",
114 "active" : "<bool>",
113 "admin" :  "<bool>",
115 "admin" :  "<bool>",
114 "ldap_dn" : "<ldap_dn>"
116 "ldap_dn" : "<ldap_dn>",
117 "last_login": "<last_login>",
118 "permissions": {
119 "global": ["hg.create.repository",
120 "repository.read",
121 "hg.register.manual_activate"],
122 "repositories": {"repo1": "repository.none"},
123 "repositories_groups": {"Group1": "group.read"}
124 },
115 }
125 }
116
126
117 error: null
127 error: null
@@ -126,6 +136,7 b' belonging to user with admin rights.'
126
136
127 INPUT::
137 INPUT::
128
138
139 id : <id_for_response>
129 api_key : "<api_key>"
140 api_key : "<api_key>"
130 method : "get_users"
141 method : "get_users"
131 args : { }
142 args : { }
@@ -141,7 +152,8 b' OUTPUT::'
141 "email" : "<email>",
152 "email" : "<email>",
142 "active" : "<bool>",
153 "active" : "<bool>",
143 "admin" :  "<bool>",
154 "admin" :  "<bool>",
144 "ldap_dn" : "<ldap_dn>"
155 "ldap_dn" : "<ldap_dn>",
156 "last_login": "<last_login>",
145 },
157 },
146 …
158 …
147 ]
159 ]
@@ -157,6 +169,7 b' be executed only using api_key belonging'
157
169
158 INPUT::
170 INPUT::
159
171
172 id : <id_for_response>
160 api_key : "<api_key>"
173 api_key : "<api_key>"
161 method : "create_user"
174 method : "create_user"
162 args : {
175 args : {
@@ -188,6 +201,7 b' be executed only using api_key belonging'
188
201
189 INPUT::
202 INPUT::
190
203
204 id : <id_for_response>
191 api_key : "<api_key>"
205 api_key : "<api_key>"
192 method : "update_user"
206 method : "update_user"
193 args : {
207 args : {
@@ -220,6 +234,7 b' belonging to user with admin rights.'
220
234
221 INPUT::
235 INPUT::
222
236
237 id : <id_for_response>
223 api_key : "<api_key>"
238 api_key : "<api_key>"
224 method : "get_users_group"
239 method : "get_users_group"
225 args : {
240 args : {
@@ -258,6 +273,7 b' api_key belonging to user with admin rig'
258
273
259 INPUT::
274 INPUT::
260
275
276 id : <id_for_response>
261 api_key : "<api_key>"
277 api_key : "<api_key>"
262 method : "get_users_groups"
278 method : "get_users_groups"
263 args : { }
279 args : { }
@@ -296,6 +312,7 b' belonging to user with admin rights'
296
312
297 INPUT::
313 INPUT::
298
314
315 id : <id_for_response>
299 api_key : "<api_key>"
316 api_key : "<api_key>"
300 method : "create_users_group"
317 method : "create_users_group"
301 args: {
318 args: {
@@ -322,6 +339,7 b' belonging to user with admin rights'
322
339
323 INPUT::
340 INPUT::
324
341
342 id : <id_for_response>
325 api_key : "<api_key>"
343 api_key : "<api_key>"
326 method : "add_user_users_group"
344 method : "add_user_users_group"
327 args: {
345 args: {
@@ -350,6 +368,7 b' using api_key belonging to user with adm'
350
368
351 INPUT::
369 INPUT::
352
370
371 id : <id_for_response>
353 api_key : "<api_key>"
372 api_key : "<api_key>"
354 method : "remove_user_from_users_group"
373 method : "remove_user_from_users_group"
355 args: {
374 args: {
@@ -370,12 +389,14 b' OUTPUT::'
370 get_repo
389 get_repo
371 --------
390 --------
372
391
373 Gets an existing repository by it's name or repository_id. This command can
392 Gets an existing repository by it's name or repository_id. Members will return
393 either users_group or user associated to that repository. This command can
374 be executed only using api_key belonging to user with admin rights.
394 be executed only using api_key belonging to user with admin rights.
375
395
376
396
377 INPUT::
397 INPUT::
378
398
399 id : <id_for_response>
379 api_key : "<api_key>"
400 api_key : "<api_key>"
380 method : "get_repo"
401 method : "get_repo"
381 args: {
402 args: {
@@ -391,7 +412,9 b' OUTPUT::'
391 "type" : "<type>",
412 "type" : "<type>",
392 "description" : "<description>",
413 "description" : "<description>",
393 "members" : [
414 "members" : [
394 { "id" : "<userid>",
415 {
416 "type": "user",
417 "id" : "<userid>",
395 "username" : "<username>",
418 "username" : "<username>",
396 "firstname": "<firstname>",
419 "firstname": "<firstname>",
397 "lastname" : "<lastname>",
420 "lastname" : "<lastname>",
@@ -402,7 +425,8 b' OUTPUT::'
402 "permission" : "repository.(read|write|admin)"
425 "permission" : "repository.(read|write|admin)"
403 },
426 },
404 …
427 …
405 {
428 {
429 "type": "users_group",
406 "id" : "<usersgroupid>",
430 "id" : "<usersgroupid>",
407 "name" : "<usersgroupname>",
431 "name" : "<usersgroupname>",
408 "active": "<bool>",
432 "active": "<bool>",
@@ -423,6 +447,7 b' belonging to user with admin rights'
423
447
424 INPUT::
448 INPUT::
425
449
450 id : <id_for_response>
426 api_key : "<api_key>"
451 api_key : "<api_key>"
427 method : "get_repos"
452 method : "get_repos"
428 args: { }
453 args: { }
@@ -452,6 +477,7 b' with admin rights'
452
477
453 INPUT::
478 INPUT::
454
479
480 id : <id_for_response>
455 api_key : "<api_key>"
481 api_key : "<api_key>"
456 method : "get_repo_nodes"
482 method : "get_repo_nodes"
457 args: {
483 args: {
@@ -485,6 +511,7 b' and create "baz" repository with "bar" a'
485
511
486 INPUT::
512 INPUT::
487
513
514 id : <id_for_response>
488 api_key : "<api_key>"
515 api_key : "<api_key>"
489 method : "create_repo"
516 method : "create_repo"
490 args: {
517 args: {
@@ -514,6 +541,7 b' belonging to user with admin rights.'
514
541
515 INPUT::
542 INPUT::
516
543
544 id : <id_for_response>
517 api_key : "<api_key>"
545 api_key : "<api_key>"
518 method : "delete_repo"
546 method : "delete_repo"
519 args: {
547 args: {
@@ -538,6 +566,7 b' with admin rights.'
538
566
539 INPUT::
567 INPUT::
540
568
569 id : <id_for_response>
541 api_key : "<api_key>"
570 api_key : "<api_key>"
542 method : "grant_user_permission"
571 method : "grant_user_permission"
543 args: {
572 args: {
@@ -563,6 +592,7 b' only using api_key belonging to user wit'
563
592
564 INPUT::
593 INPUT::
565
594
595 id : <id_for_response>
566 api_key : "<api_key>"
596 api_key : "<api_key>"
567 method : "revoke_user_permission"
597 method : "revoke_user_permission"
568 args: {
598 args: {
@@ -588,6 +618,7 b' api_key belonging to user with admin rig'
588
618
589 INPUT::
619 INPUT::
590
620
621 id : <id_for_response>
591 api_key : "<api_key>"
622 api_key : "<api_key>"
592 method : "grant_users_group_permission"
623 method : "grant_users_group_permission"
593 args: {
624 args: {
@@ -612,6 +643,7 b' executed only using api_key belonging to'
612
643
613 INPUT::
644 INPUT::
614
645
646 id : <id_for_response>
615 api_key : "<api_key>"
647 api_key : "<api_key>"
616 method : "revoke_users_group_permission"
648 method : "revoke_users_group_permission"
617 args: {
649 args: {
@@ -5,7 +5,45 b' Changelog'
5 =========
5 =========
6
6
7
7
8 1.3.4 (**2012-03-28**)
9 ----------------------
8
10
11 news
12 ++++
13
14 - Whoosh logging is now controlled by the .ini files logging setup
15 - added clone-url into edit form on /settings page
16 - added help text into repo add/edit forms
17 - created rcextensions module with additional mappings (ref #322) and
18 post push/pull/create repo hooks callbacks
19 - implemented #377 Users view for his own permissions on account page
20 - #399 added inheritance of permissions for users group on repos groups
21 - #401 repository group is automatically pre-selected when adding repos
22 inside a repository group
23 - added alternative HTTP 403 response when client failed to authenticate. Helps
24 solving issues with Mercurial and LDAP
25 - #402 removed group prefix from repository name when listing repositories
26 inside a group
27 - added gravatars into permission view and permissions autocomplete
28 - #347 when running multiple RhodeCode instances, properly invalidates cache
29 for all registered servers
30
31 fixes
32 +++++
33
34 - fixed #390 cache invalidation problems on repos inside group
35 - fixed #385 clone by ID url was loosing proxy prefix in URL
36 - fixed some unicode problems with waitress
37 - fixed issue with escaping < and > in changeset commits
38 - fixed error occurring during recursive group creation in API
39 create_repo function
40 - fixed #393 py2.5 fixes for routes url generator
41 - fixed #397 Private repository groups shows up before login
42 - fixed #396 fixed problems with revoking users in nested groups
43 - fixed mysql unicode issues + specified InnoDB as default engine with
44 utf8 charset
45 - #406 trim long branch/tag names in changelog to not break UI
46
9 1.3.3 (**2012-03-02**)
47 1.3.3 (**2012-03-02**)
10 ----------------------
48 ----------------------
11
49
@@ -23,7 +23,8 b' Users Guide'
23 usage/git_support
23 usage/git_support
24 usage/statistics
24 usage/statistics
25 usage/backup
25 usage/backup
26
26 usage/debugging
27
27 **Develop**
28 **Develop**
28
29
29 .. toctree::
30 .. toctree::
@@ -20,9 +20,10 b' following command to do this::'
20
20
21
21
22 Next, you need to create the databases used by RhodeCode. I recommend that you
22 Next, you need to create the databases used by RhodeCode. I recommend that you
23 use sqlite (default) or postgresql. If you choose a database other than the
23 use postgresql or sqlite (default). If you choose a database other than the
24 default ensure you properly adjust the db url in your production.ini
24 default ensure you properly adjust the db url in your production.ini
25 configuration file to use this other database. Create the databases by running
25 configuration file to use this other database. RhodeCode currently supports
26 postgresql, sqlite and mysql databases. Create the database by running
26 the following command::
27 the following command::
27
28
28 paster setup-app production.ini
29 paster setup-app production.ini
@@ -57,15 +58,18 b' You are now ready to use RhodeCode, to r'
57 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
58 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
58 well as edit more advanced options on users and repositories
59 well as edit more advanced options on users and repositories
59
60
60 Try copying your own mercurial repository into the "root" directory you are
61 Optionally users can create `rcextensions` package that extends RhodeCode
61 using, then from within the RhodeCode web application choose Admin >
62 functionality. To do this simply execute::
62 repositories. Then choose Add New Repository. Add the repository you copied
63
63 into the root. Test that you can browse your repository from within RhodeCode
64 paster make-rcext production.ini
64 and then try cloning your repository from RhodeCode with::
65
65
66 hg clone http://127.0.0.1:5000/<repository name>
66 This will create `rcextensions` package in the same place that your `ini` file
67 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
68 stats and add additional code into the push/pull/create repo hooks. For example
69 for sending signals to build-bots such as jenkins.
70 Please see the `__init__.py` file inside `rcextensions` package
71 for more details.
67
72
68 where *repository name* is replaced by the name of your repository.
69
73
70 Using RhodeCode with SSH
74 Using RhodeCode with SSH
71 ------------------------
75 ------------------------
@@ -47,6 +47,9 b' This will upgrade the schema and update '
47 and will always recheck the settings of the application, if there are no new
47 and will always recheck the settings of the application, if there are no new
48 options that need to be set.
48 options that need to be set.
49
49
50 .. note::
51 If you're using Celery, make sure you restart all instances of it after
52 upgrade.
50
53
51 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
54 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
52 .. _python: http://www.python.org/
55 .. _python: http://www.python.org/
@@ -71,6 +71,11 b' RhodeCode will send mails on user regist'
71 on errors the mails will have a detailed traceback of error.
71 on errors the mails will have a detailed traceback of error.
72
72
73
73
74 Mails are also sent for code comments. If someone comments on a changeset
75 mail is sent to all participants, the person who commited the changeset
76 (if present in RhodeCode), and to all people mentioned with @mention system.
77
78
74 Trending source files
79 Trending source files
75 ---------------------
80 ---------------------
76
81
@@ -93,6 +93,11 b' issue_prefix = #'
93 ## all running rhodecode instances. Leave empty if you don't use it
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
94 instance_id =
95
95
96 ## alternative return HTTP header for failed authentication. Default HTTP
97 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
98 ## handling that. Set this variable to 403 to return HTTPForbidden
99 auth_ret_code =
100
96 ####################################
101 ####################################
97 ### CELERY CONFIG ####
102 ### CELERY CONFIG ####
98 ####################################
103 ####################################
@@ -208,13 +213,13 b' logview.pylons.util = #eee'
208 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
213 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
209 sqlalchemy.db1.echo = false
214 sqlalchemy.db1.echo = false
210 sqlalchemy.db1.pool_recycle = 3600
215 sqlalchemy.db1.pool_recycle = 3600
211 sqlalchemy.convert_unicode = true
216 sqlalchemy.db1.convert_unicode = true
212
217
213 ################################
218 ################################
214 ### LOGGING CONFIGURATION ####
219 ### LOGGING CONFIGURATION ####
215 ################################
220 ################################
216 [loggers]
221 [loggers]
217 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
222 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
218
223
219 [handlers]
224 [handlers]
220 keys = console, console_sql
225 keys = console, console_sql
@@ -260,6 +265,12 b' handlers = console_sql'
260 qualname = sqlalchemy.engine
265 qualname = sqlalchemy.engine
261 propagate = 0
266 propagate = 0
262
267
268 [logger_whoosh_indexer]
269 level = DEBUG
270 handlers =
271 qualname = whoosh_indexer
272 propagate = 1
273
263 ##############
274 ##############
264 ## HANDLERS ##
275 ## HANDLERS ##
265 ##############
276 ##############
@@ -1,17 +1,17 b''
1 Pylons==1.0.0
1 Pylons==1.0.0
2 Beaker==1.6.3
2 Beaker==1.6.3
3 WebHelpers>=1.2
3 WebHelpers==1.3
4 formencode==1.2.4
4 formencode==1.2.4
5 SQLAlchemy==0.7.4
5 SQLAlchemy==0.7.6
6 Mako==0.5.0
6 Mako==0.6.2
7 pygments>=1.4
7 pygments>=1.4
8 whoosh>=2.3.0,<2.4
8 whoosh>=2.3.0,<2.4
9 celery>=2.2.5,<2.3
9 celery>=2.2.5,<2.3
10 babel
10 babel
11 python-dateutil>=1.5.0,<2.0.0
11 python-dateutil>=1.5.0,<2.0.0
12 dulwich>=0.8.0,<0.9.0
12 https://github.com/jelmer/dulwich/tarball/master
13 webob==1.0.8
13 webob==1.0.8
14 markdown==2.1.1
14 markdown==2.1.1
15 docutils==0.8.1
15 docutils==0.8.1
16 py-bcrypt
16 py-bcrypt
17 mercurial>=2.1,<2.2 No newline at end of file
17 mercurial>=2.1,<2.2
@@ -4,7 +4,7 b''
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://semver.org/
7 versioning implementation: http://www.python.org/dev/peps/pep-0386/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 2010
10 :author: marcink
10 :author: marcink
@@ -26,8 +26,18 b''
26 import sys
26 import sys
27 import platform
27 import platform
28
28
29 VERSION = (1, 3, 3)
29 VERSION = (1, 3, 4)
30 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30
31 try:
32 from rhodecode.lib import get_current_revision
33 _rev = get_current_revision()
34 if _rev:
35 VERSION += ('dev%s' % _rev[0],)
36 except ImportError:
37 pass
38
39 __version__ = ('.'.join((str(each) for each in VERSION[:3])) +
40 '.'.join(VERSION[3:]))
31 __dbversion__ = 5 # defines current db version for migrations
41 __dbversion__ = 5 # defines current db version for migrations
32 __platform__ = platform.system()
42 __platform__ = platform.system()
33 __license__ = 'GPLv3'
43 __license__ = 'GPLv3'
@@ -39,16 +49,16 b" PLATFORM_OTHERS = ('Linux', 'Darwin', 'F"
39 requirements = [
49 requirements = [
40 "Pylons==1.0.0",
50 "Pylons==1.0.0",
41 "Beaker==1.6.3",
51 "Beaker==1.6.3",
42 "WebHelpers>=1.2",
52 "WebHelpers==1.3",
43 "formencode==1.2.4",
53 "formencode==1.2.4",
44 "SQLAlchemy==0.7.4",
54 "SQLAlchemy==0.7.6",
45 "Mako==0.5.0",
55 "Mako==0.6.2",
46 "pygments>=1.4",
56 "pygments>=1.4",
47 "whoosh>=2.3.0,<2.4",
57 "whoosh>=2.3.0,<2.4",
48 "celery>=2.2.5,<2.3",
58 "celery>=2.2.5,<2.3",
49 "babel",
59 "babel",
50 "python-dateutil>=1.5.0,<2.0.0",
60 "python-dateutil>=1.5.0,<2.0.0",
51 "dulwich>=0.8.0,<0.9.0",
61 "dulwich>=0.8.4,<0.9.0",
52 "webob==1.0.8",
62 "webob==1.0.8",
53 "markdown==2.1.1",
63 "markdown==2.1.1",
54 "docutils==0.8.1",
64 "docutils==0.8.1",
@@ -65,17 +75,6 b' else:'
65 requirements.append("mercurial>=2.1,<2.2")
75 requirements.append("mercurial>=2.1,<2.2")
66
76
67
77
68 try:
69 from rhodecode.lib import get_current_revision
70 _rev = get_current_revision(quiet=True)
71 except ImportError:
72 # this is needed when doing some setup.py operations
73 _rev = False
74
75 if len(VERSION) > 3 and _rev:
76 __version__ += ' [rev:%s]' % _rev[0]
77
78
79 def get_version():
78 def get_version():
80 """Returns shorter version (digit parts only) as string."""
79 """Returns shorter version (digit parts only) as string."""
81
80
@@ -90,3 +89,6 b' CELERY_ON = False'
90
89
91 # link to config for pylons
90 # link to config for pylons
92 CONFIG = {}
91 CONFIG = {}
92
93 # Linked module for extensions
94 EXTENSIONS = {}
@@ -93,6 +93,11 b' issue_prefix = #'
93 ## all running rhodecode instances. Leave empty if you don't use it
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
94 instance_id =
95
95
96 ## alternative return HTTP header for failed authentication. Default HTTP
97 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
98 ## handling that. Set this variable to 403 to return HTTPForbidden
99 auth_ret_code =
100
96 ####################################
101 ####################################
97 ### CELERY CONFIG ####
102 ### CELERY CONFIG ####
98 ####################################
103 ####################################
@@ -218,13 +223,13 b' sqlalchemy.db1.url = sqlite:///%(here)s/'
218
223
219 sqlalchemy.db1.echo = false
224 sqlalchemy.db1.echo = false
220 sqlalchemy.db1.pool_recycle = 3600
225 sqlalchemy.db1.pool_recycle = 3600
221 sqlalchemy.convert_unicode = true
226 sqlalchemy.db1.convert_unicode = true
222
227
223 ################################
228 ################################
224 ### LOGGING CONFIGURATION ####
229 ### LOGGING CONFIGURATION ####
225 ################################
230 ################################
226 [loggers]
231 [loggers]
227 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
232 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
228
233
229 [handlers]
234 [handlers]
230 keys = console, console_sql
235 keys = console, console_sql
@@ -270,6 +275,12 b' handlers = console_sql'
270 qualname = sqlalchemy.engine
275 qualname = sqlalchemy.engine
271 propagate = 0
276 propagate = 0
272
277
278 [logger_whoosh_indexer]
279 level = DEBUG
280 handlers =
281 qualname = whoosh_indexer
282 propagate = 1
283
273 ##############
284 ##############
274 ## HANDLERS ##
285 ## HANDLERS ##
275 ##############
286 ##############
@@ -2,21 +2,24 b''
2
2
3 import os
3 import os
4 import logging
4 import logging
5 import rhodecode
5
6
6 from mako.lookup import TemplateLookup
7 from mako.lookup import TemplateLookup
7 from pylons.configuration import PylonsConfig
8 from pylons.configuration import PylonsConfig
8 from pylons.error import handle_mako_error
9 from pylons.error import handle_mako_error
9
10
10 import rhodecode
11 # don't remove this import it does magic for celery
12 from rhodecode.lib import celerypylons
13
11 import rhodecode.lib.app_globals as app_globals
14 import rhodecode.lib.app_globals as app_globals
12 import rhodecode.lib.helpers
13
15
14 from rhodecode.config.routing import make_map
16 from rhodecode.config.routing import make_map
15 # don't remove this import it does magic for celery
17
16 from rhodecode.lib import celerypylons, str2bool
18 from rhodecode.lib import helpers
17 from rhodecode.lib import engine_from_config
18 from rhodecode.lib.auth import set_available_permissions
19 from rhodecode.lib.auth import set_available_permissions
19 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
20 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config,\
21 load_rcextensions
22 from rhodecode.lib.utils2 import engine_from_config, str2bool
20 from rhodecode.model import init_model
23 from rhodecode.model import init_model
21 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
22
25
@@ -24,17 +27,20 b' log = logging.getLogger(__name__)'
24
27
25
28
26 def load_environment(global_conf, app_conf, initial=False):
29 def load_environment(global_conf, app_conf, initial=False):
27 """Configure the Pylons environment via the ``pylons.config``
30 """
31 Configure the Pylons environment via the ``pylons.config``
28 object
32 object
29 """
33 """
30 config = PylonsConfig()
34 config = PylonsConfig()
31
35
32 # Pylons paths
36 # Pylons paths
33 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
37 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
34 paths = dict(root=root,
38 paths = dict(
35 controllers=os.path.join(root, 'controllers'),
39 root=root,
36 static_files=os.path.join(root, 'public'),
40 controllers=os.path.join(root, 'controllers'),
37 templates=[os.path.join(root, 'templates')])
41 static_files=os.path.join(root, 'public'),
42 templates=[os.path.join(root, 'templates')]
43 )
38
44
39 # Initialize config with the basic options
45 # Initialize config with the basic options
40 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
46 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
@@ -44,8 +50,11 b' def load_environment(global_conf, app_co'
44
50
45 config['routes.map'] = make_map(config)
51 config['routes.map'] = make_map(config)
46 config['pylons.app_globals'] = app_globals.Globals(config)
52 config['pylons.app_globals'] = app_globals.Globals(config)
47 config['pylons.h'] = rhodecode.lib.helpers
53 config['pylons.h'] = helpers
48 rhodecode.CONFIG = config
54 rhodecode.CONFIG = config
55
56 load_rcextensions(root_path=config['here'])
57
49 # Setup cache object as early as possible
58 # Setup cache object as early as possible
50 import pylons
59 import pylons
51 pylons.cache._push_object(config['pylons.app_globals'].cache)
60 pylons.cache._push_object(config['pylons.app_globals'].cache)
@@ -284,7 +284,6 b' class ReposController(BaseController):'
284
284
285 :param repo_name:
285 :param repo_name:
286 """
286 """
287
288 try:
287 try:
289 RepoModel().revoke_user_permission(repo=repo_name,
288 RepoModel().revoke_user_permission(repo=repo_name,
290 user=request.POST['user_id'])
289 user=request.POST['user_id'])
@@ -145,11 +145,12 b' class UsersController(BaseController):'
145 user_model = UserModel()
145 user_model = UserModel()
146 try:
146 try:
147 user_model.delete(id)
147 user_model.delete(id)
148 Session.commit()
148 h.flash(_('successfully deleted user'), category='success')
149 h.flash(_('successfully deleted user'), category='success')
149 Session.commit()
150 except (UserOwnsReposException, DefaultUserException), e:
150 except (UserOwnsReposException, DefaultUserException), e:
151 h.flash(str(e), category='warning')
151 h.flash(e, category='warning')
152 except Exception:
152 except Exception:
153 log.error(traceback.format_exc())
153 h.flash(_('An error occurred during deletion of user'),
154 h.flash(_('An error occurred during deletion of user'),
154 category='error')
155 category='error')
155 return redirect(url('users'))
156 return redirect(url('users'))
@@ -32,8 +32,9 b' from pylons import request, session, tmp'
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
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.exceptions import UsersGroupsAssignedException
36 from rhodecode.lib.exceptions import UsersGroupsAssignedException
36 from rhodecode.lib import helpers as h, safe_unicode
37 from rhodecode.lib.utils2 import safe_unicode
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
39
40
@@ -233,10 +233,10 b' class JSONRPCController(WSGIController):'
233 try:
233 try:
234 return json.dumps(response)
234 return json.dumps(response)
235 except TypeError, e:
235 except TypeError, e:
236 log.debug('Error encoding response: %s' % e)
236 log.error('API FAILED. Error encoding response: %s' % e)
237 return json.dumps(
237 return json.dumps(
238 dict(
238 dict(
239 self._req_id,
239 id=self._req_id,
240 result=None,
240 result=None,
241 error="Error encoding response"
241 error="Error encoding response"
242 )
242 )
@@ -30,16 +30,15 b' import logging'
30
30
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator, PasswordGenerator
33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
34
34
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
37 from rhodecode.model.db import User, UsersGroup, Repository
38 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.users_group import UsersGroupModel
40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.repos_group import ReposGroupModel
41 from rhodecode.lib.utils import map_groups
42
43
42
44 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
45
44
@@ -100,7 +99,9 b' class ApiController(JSONRPCController):'
100 email=user.email,
99 email=user.email,
101 active=user.active,
100 active=user.active,
102 admin=user.admin,
101 admin=user.admin,
103 ldap_dn=user.ldap_dn
102 ldap_dn=user.ldap_dn,
103 last_login=user.last_login,
104 permissions=AuthUser(user_id=user.user_id).permissions
104 )
105 )
105
106
106 @HasPermissionAllDecorator('hg.admin')
107 @HasPermissionAllDecorator('hg.admin')
@@ -122,7 +123,8 b' class ApiController(JSONRPCController):'
122 email=user.email,
123 email=user.email,
123 active=user.active,
124 active=user.active,
124 admin=user.admin,
125 admin=user.admin,
125 ldap_dn=user.ldap_dn
126 ldap_dn=user.ldap_dn,
127 last_login=user.last_login,
126 )
128 )
127 )
129 )
128 return result
130 return result
@@ -282,7 +284,7 b' class ApiController(JSONRPCController):'
282 @HasPermissionAllDecorator('hg.admin')
284 @HasPermissionAllDecorator('hg.admin')
283 def add_user_to_users_group(self, apiuser, group_name, username):
285 def add_user_to_users_group(self, apiuser, group_name, username):
284 """"
286 """"
285 Add a user to a group
287 Add a user to a users group
286
288
287 :param apiuser:
289 :param apiuser:
288 :param group_name:
290 :param group_name:
@@ -360,7 +362,7 b' class ApiController(JSONRPCController):'
360 user = user.user
362 user = user.user
361 members.append(
363 members.append(
362 dict(
364 dict(
363 type_="user",
365 type="user",
364 id=user.user_id,
366 id=user.user_id,
365 username=user.username,
367 username=user.username,
366 firstname=user.name,
368 firstname=user.name,
@@ -377,7 +379,7 b' class ApiController(JSONRPCController):'
377 users_group = users_group.users_group
379 users_group = users_group.users_group
378 members.append(
380 members.append(
379 dict(
381 dict(
380 type_="users_group",
382 type="users_group",
381 id=users_group.users_group_id,
383 id=users_group.users_group_id,
382 name=users_group.users_group_name,
384 name=users_group.users_group_name,
383 active=users_group.users_group_active,
385 active=users_group.users_group_active,
@@ -464,15 +466,10 b' class ApiController(JSONRPCController):'
464 if Repository.get_by_repo_name(repo_name):
466 if Repository.get_by_repo_name(repo_name):
465 raise JSONRPCError("repo %s already exist" % repo_name)
467 raise JSONRPCError("repo %s already exist" % repo_name)
466
468
467 groups = repo_name.split('/')
469 groups = repo_name.split(Repository.url_sep())
468 real_name = groups[-1]
470 real_name = groups[-1]
469 groups = groups[:-1]
471 # create structure of groups
470 parent_id = None
472 group = map_groups(repo_name)
471 for g in groups:
472 group = RepoGroup.get_by_group_name(g)
473 if not group:
474 group = ReposGroupModel().create(g, '', parent_id)
475 parent_id = group.group_id
476
473
477 repo = RepoModel().create(
474 repo = RepoModel().create(
478 dict(
475 dict(
@@ -481,7 +478,7 b' class ApiController(JSONRPCController):'
481 description=description,
478 description=description,
482 private=private,
479 private=private,
483 repo_type=repo_type,
480 repo_type=repo_type,
484 repo_group=parent_id,
481 repo_group=group.group_id if group else None,
485 clone_uri=clone_uri
482 clone_uri=clone_uri
486 ),
483 ),
487 owner
484 owner
@@ -31,7 +31,7 b' import binascii'
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.compat import OrderedDict
33 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib import safe_unicode
34 from rhodecode.lib.utils2 import safe_unicode
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
@@ -51,13 +51,18 b' from rhodecode.lib.diffs import wrapped_'
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def anchor_url(revision, path):
54 def _update_with_GET(params, GET):
55 for k in ['diff1', 'diff2', 'diff']:
56 params[k] += GET.getall(k)
57
58
59 def anchor_url(revision, path, GET):
55 fid = h.FID(revision, path)
60 fid = h.FID(revision, path)
56 return h.url.current(anchor=fid, **dict(request.GET))
61 return h.url.current(anchor=fid, **dict(GET))
57
62
58
63
59 def get_ignore_ws(fid, GET):
64 def get_ignore_ws(fid, GET):
60 ig_ws_global = request.GET.get('ignorews')
65 ig_ws_global = GET.get('ignorews')
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
66 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 if ig_ws:
67 if ig_ws:
63 try:
68 try:
@@ -67,12 +72,13 b' def get_ignore_ws(fid, GET):'
67 return ig_ws_global
72 return ig_ws_global
68
73
69
74
70 def _ignorews_url(fileid=None):
75 def _ignorews_url(GET, fileid=None):
71
76 fileid = str(fileid) if fileid else None
72 params = defaultdict(list)
77 params = defaultdict(list)
78 _update_with_GET(params, GET)
73 lbl = _('show white space')
79 lbl = _('show white space')
74 ig_ws = get_ignore_ws(fileid, request.GET)
80 ig_ws = get_ignore_ws(fileid, GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
81 ln_ctx = get_line_ctx(fileid, GET)
76 # global option
82 # global option
77 if fileid is None:
83 if fileid is None:
78 if ig_ws is None:
84 if ig_ws is None:
@@ -98,7 +104,7 b' def _ignorews_url(fileid=None):'
98
104
99
105
100 def get_line_ctx(fid, GET):
106 def get_line_ctx(fid, GET):
101 ln_ctx_global = request.GET.get('context')
107 ln_ctx_global = GET.get('context')
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
108 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103
109
104 if ln_ctx:
110 if ln_ctx:
@@ -112,16 +118,19 b' def get_line_ctx(fid, GET):'
112 return
118 return
113
119
114
120
115 def _context_url(fileid=None):
121 def _context_url(GET, fileid=None):
116 """
122 """
117 Generates url for context lines
123 Generates url for context lines
118
124
119 :param fileid:
125 :param fileid:
120 """
126 """
121 ig_ws = get_ignore_ws(fileid, request.GET)
127
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
128 fileid = str(fileid) if fileid else None
129 ig_ws = get_ignore_ws(fileid, GET)
130 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
123
131
124 params = defaultdict(list)
132 params = defaultdict(list)
133 _update_with_GET(params, GET)
125
134
126 # global option
135 # global option
127 if fileid is None:
136 if fileid is None:
@@ -162,7 +171,7 b' class ChangesetController(BaseRepoContro'
162 c.anchor_url = anchor_url
171 c.anchor_url = anchor_url
163 c.ignorews_url = _ignorews_url
172 c.ignorews_url = _ignorews_url
164 c.context_url = _context_url
173 c.context_url = _context_url
165
174 limit_off = request.GET.get('fulldiff')
166 #get ranges of revisions if preset
175 #get ranges of revisions if preset
167 rev_range = revision.split('...')[:2]
176 rev_range = revision.split('...')[:2]
168 enable_comments = True
177 enable_comments = True
@@ -220,7 +229,7 b' class ChangesetController(BaseRepoContro'
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
229 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 lim = self.cut_off_limit
230 lim = self.cut_off_limit
222 if cumulative_diff > self.cut_off_limit:
231 if cumulative_diff > self.cut_off_limit:
223 lim = -1
232 lim = -1 if limit_off is None else None
224 size, cs1, cs2, diff, st = wrapped_diff(
233 size, cs1, cs2, diff, st = wrapped_diff(
225 filenode_old=None,
234 filenode_old=None,
226 filenode_new=node,
235 filenode_new=node,
@@ -251,7 +260,7 b' class ChangesetController(BaseRepoContro'
251 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
260 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
252 lim = self.cut_off_limit
261 lim = self.cut_off_limit
253 if cumulative_diff > self.cut_off_limit:
262 if cumulative_diff > self.cut_off_limit:
254 lim = -1
263 lim = -1 if limit_off is None else None
255 size, cs1, cs2, diff, st = wrapped_diff(
264 size, cs1, cs2, diff, st = wrapped_diff(
256 filenode_old=filenode_old,
265 filenode_old=filenode_old,
257 filenode_new=node,
266 filenode_new=node,
@@ -28,7 +28,7 b' import logging'
28 from pylons import url, response, tmpl_context as c
28 from pylons import url, response, tmpl_context as c
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.lib.utils2 import safe_unicode
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseRepoController
33 from rhodecode.lib.base import BaseRepoController
34
34
@@ -32,24 +32,26 b' from pylons.i18n.translation import _'
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33 from pylons.decorators import jsonify
33 from pylons.decorators import jsonify
34
34
35 from rhodecode.lib.vcs.conf import settings
35 from rhodecode.lib import diffs
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
36 from rhodecode.lib import helpers as h
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
38 NodeAlreadyExistsError
39 from rhodecode.lib.vcs.nodes import FileNode
40
37
41 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
39 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.utils import EmptyChangeset
42 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib import diffs
43 from rhodecode.lib.vcs.conf import settings
47 import rhodecode.lib.helpers as h
44 from rhodecode.lib.vcs.exceptions import RepositoryError, \
45 ChangesetDoesNotExistError, EmptyRepositoryError, \
46 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
47 from rhodecode.lib.vcs.nodes import FileNode
48
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.scm import ScmModel
51
49 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
52 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
50 _context_url, get_line_ctx, get_ignore_ws
53 _context_url, get_line_ctx, get_ignore_ws
51 from rhodecode.lib.diffs import wrapped_diff
54
52 from rhodecode.model.scm import ScmModel
53
55
54 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
55
57
@@ -447,7 +449,7 b' class FilesController(BaseRepoController'
447 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
449 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
448
450
449 lim = request.GET.get('fulldiff') or self.cut_off_limit
451 lim = request.GET.get('fulldiff') or self.cut_off_limit
450 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
452 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
451 filenode_new=node2,
453 filenode_new=node2,
452 cut_off_limit=lim,
454 cut_off_limit=lim,
453 ignore_whitespace=ign_whitespace_lcl,
455 ignore_whitespace=ign_whitespace_lcl,
@@ -44,7 +44,7 b' class HomeController(BaseController):'
44 def index(self):
44 def index(self):
45 c.repos_list = self.scm_model.get_repos()
45 c.repos_list = self.scm_model.get_repos()
46 c.groups = self.scm_model.get_repos_groups()
46 c.groups = self.scm_model.get_repos_groups()
47
47 c.group = None
48 return render('/index.html')
48 return render('/index.html')
49
49
50 def repo_switcher(self):
50 def repo_switcher(self):
@@ -26,6 +26,7 b''
26 import traceback
26 import traceback
27 import calendar
27 import calendar
28 import logging
28 import logging
29 import urllib
29 from time import mktime
30 from time import mktime
30 from datetime import timedelta, date
31 from datetime import timedelta, date
31 from urlparse import urlparse
32 from urlparse import urlparse
@@ -39,15 +40,15 b' from pylons.i18n.translation import _'
39
40
40 from beaker.cache import cache_region, region_invalidate
41 from beaker.cache import cache_region, region_invalidate
41
42
43 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
42 from rhodecode.model.db import Statistics, CacheInvalidation
44 from rhodecode.model.db import Statistics, CacheInvalidation
43 from rhodecode.lib import ALL_READMES, ALL_EXTS
45 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
46 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.utils import EmptyChangeset
48 from rhodecode.lib.utils import EmptyChangeset
47 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.celerylib import run_task
50 from rhodecode.lib.celerylib import run_task
49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
51 from rhodecode.lib.celerylib.tasks import get_commits_stats
50 LANGUAGES_EXTENSIONS_MAP
51 from rhodecode.lib.helpers import RepoPage
52 from rhodecode.lib.helpers import RepoPage
52 from rhodecode.lib.compat import json, OrderedDict
53 from rhodecode.lib.compat import json, OrderedDict
53
54
@@ -91,34 +92,37 b' class SummaryController(BaseRepoControll'
91
92
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94
95 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
95 uri_dict = {
96 uri_dict = {
96 'user': username,
97 'user': username,
97 'pass': password,
98 'pass': password,
98 'scheme': parsed_url.scheme,
99 'scheme': parsed_url.scheme,
99 'netloc': parsed_url.netloc,
100 'netloc': parsed_url.netloc,
100 'path': parsed_url.path
101 'path': decoded_path
101 }
102 }
103
102 uri = uri_tmpl % uri_dict
104 uri = uri_tmpl % uri_dict
103 # generate another clone url by id
105 # generate another clone url by id
104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
106 uri_dict.update(
107 {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
108 )
105 uri_id = uri_tmpl % uri_dict
109 uri_id = uri_tmpl % uri_dict
106
110
107 c.clone_repo_url = uri
111 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
112 c.clone_repo_url_id = uri_id
109 c.repo_tags = OrderedDict()
113 c.repo_tags = OrderedDict()
110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
114 for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
111 try:
115 try:
112 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
116 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
113 except ChangesetError:
117 except ChangesetError:
114 c.repo_tags[name] = EmptyChangeset(hash)
118 c.repo_tags[name] = EmptyChangeset(hash_)
115
119
116 c.repo_branches = OrderedDict()
120 c.repo_branches = OrderedDict()
117 for name, hash in c.rhodecode_repo.branches.items()[:10]:
121 for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
118 try:
122 try:
119 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
123 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
120 except ChangesetError:
124 except ChangesetError:
121 c.repo_branches[name] = EmptyChangeset(hash)
125 c.repo_branches[name] = EmptyChangeset(hash_)
122
126
123 td = date.today() + timedelta(days=1)
127 td = date.today() + timedelta(days=1)
124 td_1m = td - timedelta(days=calendar.mdays[td.month])
128 td_1m = td - timedelta(days=calendar.mdays[td.month])
@@ -175,7 +179,7 b' class SummaryController(BaseRepoControll'
175 if c.enable_downloads:
179 if c.enable_downloads:
176 c.download_options = self._get_download_links(c.rhodecode_repo)
180 c.download_options = self._get_download_links(c.rhodecode_repo)
177
181
178 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
182 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_db_repo)
179 return render('summary/summary.html')
183 return render('summary/summary.html')
180
184
181 def __get_readme_data(self, repo):
185 def __get_readme_data(self, repo):
@@ -206,7 +210,7 b' class SummaryController(BaseRepoControll'
206
210
207 return readme_data, readme_file
211 return readme_data, readme_file
208
212
209 key = repo.name + '_README'
213 key = repo.repo_name + '_README'
210 inv = CacheInvalidation.invalidate(key)
214 inv = CacheInvalidation.invalidate(key)
211 if inv is not None:
215 if inv is not None:
212 region_invalidate(_get_readme_from_cache, None, key)
216 region_invalidate(_get_readme_from_cache, None, key)
@@ -1,432 +1,4 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
6 Some simple helper functions
7
8 :created_on: Jan 5, 2011
9 :author: marcink
10 :copyright: (C) 2011-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 import os
1 import os
27 import re
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29
30
31 def __get_lem():
32 from pygments import lexers
33 from string import lower
34 from collections import defaultdict
35
36 d = defaultdict(lambda: [])
37
38 def __clean(s):
39 s = s.lstrip('*')
40 s = s.lstrip('.')
41
42 if s.find('[') != -1:
43 exts = []
44 start, stop = s.find('['), s.find(']')
45
46 for suffix in s[start + 1:stop]:
47 exts.append(s[:s.find('[')] + suffix)
48 return map(lower, exts)
49 else:
50 return map(lower, [s])
51
52 for lx, t in sorted(lexers.LEXERS.items()):
53 m = map(__clean, t[-2])
54 if m:
55 m = reduce(lambda x, y: x + y, m)
56 for ext in m:
57 desc = lx.replace('Lexer', '')
58 d[ext].append(desc)
59
60 return dict(d)
61
62 # language map is also used by whoosh indexer, which for those specified
63 # extensions will index it's content
64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
65
66 # Additional mappings that are not present in the pygments lexers
67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
69
70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
71
72 # list of readme files to search in file tree and display in summary
73 # attached weights defines the search order lower is first
74 ALL_READMES = [
75 ('readme', 0), ('README', 0), ('Readme', 0),
76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
80 ]
81
82 # extension together with weights to search lower is first
83 RST_EXTS = [
84 ('', 0), ('.rst', 1), ('.rest', 1),
85 ('.RST', 2), ('.REST', 2),
86 ('.txt', 3), ('.TXT', 3)
87 ]
88
89 MARKDOWN_EXTS = [
90 ('.md', 1), ('.MD', 1),
91 ('.mkdn', 2), ('.MKDN', 2),
92 ('.mdown', 3), ('.MDOWN', 3),
93 ('.markdown', 4), ('.MARKDOWN', 4)
94 ]
95
96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
97
98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
99
100
101 def str2bool(_str):
102 """
103 returs True/False value from given string, it tries to translate the
104 string into boolean
105
106 :param _str: string value to translate into boolean
107 :rtype: boolean
108 :returns: boolean from given string
109 """
110 if _str is None:
111 return False
112 if _str in (True, False):
113 return _str
114 _str = str(_str).strip().lower()
115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
116
117
118 def convert_line_endings(line, mode):
119 """
120 Converts a given line "line end" accordingly to given mode
121
122 Available modes are::
123 0 - Unix
124 1 - Mac
125 2 - DOS
126
127 :param line: given line to convert
128 :param mode: mode to convert to
129 :rtype: str
130 :return: converted line according to mode
131 """
132 from string import replace
133
134 if mode == 0:
135 line = replace(line, '\r\n', '\n')
136 line = replace(line, '\r', '\n')
137 elif mode == 1:
138 line = replace(line, '\r\n', '\r')
139 line = replace(line, '\n', '\r')
140 elif mode == 2:
141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
142 return line
143
144
145 def detect_mode(line, default):
146 """
147 Detects line break for given line, if line break couldn't be found
148 given default value is returned
149
150 :param line: str line
151 :param default: default
152 :rtype: int
153 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
154 """
155 if line.endswith('\r\n'):
156 return 2
157 elif line.endswith('\n'):
158 return 0
159 elif line.endswith('\r'):
160 return 1
161 else:
162 return default
163
164
165 def generate_api_key(username, salt=None):
166 """
167 Generates unique API key for given username, if salt is not given
168 it'll be generated from some random string
169
170 :param username: username as string
171 :param salt: salt to hash generate KEY
172 :rtype: str
173 :returns: sha1 hash from username+salt
174 """
175 from tempfile import _RandomNameSequence
176 import hashlib
177
178 if salt is None:
179 salt = _RandomNameSequence().next()
180
181 return hashlib.sha1(username + salt).hexdigest()
182
183
184 def safe_unicode(str_, from_encoding=None):
185 """
186 safe unicode function. Does few trick to turn str_ into unicode
187
188 In case of UnicodeDecode error we try to return it with encoding detected
189 by chardet library if it fails fallback to unicode with errors replaced
190
191 :param str_: string to decode
192 :rtype: unicode
193 :returns: unicode object
194 """
195 if isinstance(str_, unicode):
196 return str_
197
198 if not from_encoding:
199 import rhodecode
200 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
201 from_encoding = DEFAULT_ENCODING
202
203 try:
204 return unicode(str_)
205 except UnicodeDecodeError:
206 pass
207
208 try:
209 return unicode(str_, from_encoding)
210 except UnicodeDecodeError:
211 pass
212
213 try:
214 import chardet
215 encoding = chardet.detect(str_)['encoding']
216 if encoding is None:
217 raise Exception()
218 return str_.decode(encoding)
219 except (ImportError, UnicodeDecodeError, Exception):
220 return unicode(str_, from_encoding, 'replace')
221
222
223 def safe_str(unicode_, to_encoding=None):
224 """
225 safe str function. Does few trick to turn unicode_ into string
226
227 In case of UnicodeEncodeError we try to return it with encoding detected
228 by chardet library if it fails fallback to string with errors replaced
229
230 :param unicode_: unicode to encode
231 :rtype: str
232 :returns: str object
233 """
234
235 # if it's not basestr cast to str
236 if not isinstance(unicode_, basestring):
237 return str(unicode_)
238
239 if isinstance(unicode_, str):
240 return unicode_
241
242 if not to_encoding:
243 import rhodecode
244 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
245 to_encoding = DEFAULT_ENCODING
246
247 try:
248 return unicode_.encode(to_encoding)
249 except UnicodeEncodeError:
250 pass
251
252 try:
253 import chardet
254 encoding = chardet.detect(unicode_)['encoding']
255 print encoding
256 if encoding is None:
257 raise UnicodeEncodeError()
258
259 return unicode_.encode(encoding)
260 except (ImportError, UnicodeEncodeError):
261 return unicode_.encode(to_encoding, 'replace')
262
263 return safe_str
264
265
266 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
267 """
268 Custom engine_from_config functions that makes sure we use NullPool for
269 file based sqlite databases. This prevents errors on sqlite. This only
270 applies to sqlalchemy versions < 0.7.0
271
272 """
273 import sqlalchemy
274 from sqlalchemy import engine_from_config as efc
275 import logging
276
277 if int(sqlalchemy.__version__.split('.')[1]) < 7:
278
279 # This solution should work for sqlalchemy < 0.7.0, and should use
280 # proxy=TimerProxy() for execution time profiling
281
282 from sqlalchemy.pool import NullPool
283 url = configuration[prefix + 'url']
284
285 if url.startswith('sqlite'):
286 kwargs.update({'poolclass': NullPool})
287 return efc(configuration, prefix, **kwargs)
288 else:
289 import time
290 from sqlalchemy import event
291 from sqlalchemy.engine import Engine
292
293 log = logging.getLogger('sqlalchemy.engine')
294 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
295 engine = efc(configuration, prefix, **kwargs)
296
297 def color_sql(sql):
298 COLOR_SEQ = "\033[1;%dm"
299 COLOR_SQL = YELLOW
300 normal = '\x1b[0m'
301 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
302
303 if configuration['debug']:
304 #attach events only for debug configuration
305
306 def before_cursor_execute(conn, cursor, statement,
307 parameters, context, executemany):
308 context._query_start_time = time.time()
309 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
310
311
312 def after_cursor_execute(conn, cursor, statement,
313 parameters, context, executemany):
314 total = time.time() - context._query_start_time
315 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
316
317 event.listen(engine, "before_cursor_execute",
318 before_cursor_execute)
319 event.listen(engine, "after_cursor_execute",
320 after_cursor_execute)
321
322 return engine
323
324
325 def age(curdate):
326 """
327 turns a datetime into an age string.
328
329 :param curdate: datetime object
330 :rtype: unicode
331 :returns: unicode words describing age
332 """
333
334 from datetime import datetime
335 from webhelpers.date import time_ago_in_words
336
337 _ = lambda s: s
338
339 if not curdate:
340 return ''
341
342 agescales = [(_(u"year"), 3600 * 24 * 365),
343 (_(u"month"), 3600 * 24 * 30),
344 (_(u"day"), 3600 * 24),
345 (_(u"hour"), 3600),
346 (_(u"minute"), 60),
347 (_(u"second"), 1), ]
348
349 age = datetime.now() - curdate
350 age_seconds = (age.days * agescales[2][1]) + age.seconds
351 pos = 1
352 for scale in agescales:
353 if scale[1] <= age_seconds:
354 if pos == 6:
355 pos = 5
356 return '%s %s' % (time_ago_in_words(curdate,
357 agescales[pos][0]), _('ago'))
358 pos += 1
359
360 return _(u'just now')
361
362
363 def uri_filter(uri):
364 """
365 Removes user:password from given url string
366
367 :param uri:
368 :rtype: unicode
369 :returns: filtered list of strings
370 """
371 if not uri:
372 return ''
373
374 proto = ''
375
376 for pat in ('https://', 'http://'):
377 if uri.startswith(pat):
378 uri = uri[len(pat):]
379 proto = pat
380 break
381
382 # remove passwords and username
383 uri = uri[uri.find('@') + 1:]
384
385 # get the port
386 cred_pos = uri.find(':')
387 if cred_pos == -1:
388 host, port = uri, None
389 else:
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391
392 return filter(None, [proto, host, port])
393
394
395 def credentials_filter(uri):
396 """
397 Returns a url with removed credentials
398
399 :param uri:
400 """
401
402 uri = uri_filter(uri)
403 #check if we have port
404 if len(uri) > 2 and uri[2]:
405 uri[2] = ':' + uri[2]
406
407 return ''.join(uri)
408
409
410 def get_changeset_safe(repo, rev):
411 """
412 Safe version of get_changeset if this changeset doesn't exists for a
413 repo it returns a Dummy one instead
414
415 :param repo:
416 :param rev:
417 """
418 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.exceptions import RepositoryError
420 if not isinstance(repo, BaseRepository):
421 raise Exception('You must pass an Repository '
422 'object as first argument got %s', type(repo))
423
424 try:
425 cs = repo.get_changeset(rev)
426 except RepositoryError:
427 from rhodecode.lib.utils import EmptyChangeset
428 cs = EmptyChangeset(requested_revision=rev)
429 return cs
430
2
431
3
432 def get_current_revision(quiet=False):
4 def get_current_revision(quiet=False):
@@ -450,16 +22,3 b' def get_current_revision(quiet=False):'
450 print ("Cannot retrieve rhodecode's revision. Original error "
22 print ("Cannot retrieve rhodecode's revision. Original error "
451 "was: %s" % err)
23 "was: %s" % err)
452 return None
24 return None
453
454
455 def extract_mentioned_users(s):
456 """
457 Returns unique usernames from given string s that have @mention
458
459 :param s: string to get mentions
460 """
461 usrs = {}
462 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
463 usrs[username] = username
464
465 return sorted(usrs.keys())
@@ -43,7 +43,7 b' if __platform__ in PLATFORM_WIN:'
43 if __platform__ in PLATFORM_OTHERS:
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
44 import bcrypt
45
45
46 from rhodecode.lib import str2bool, safe_unicode
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
@@ -521,8 +521,7 b' class PermsDecorator(object):'
521 self.user = cls.rhodecode_user
521 self.user = cls.rhodecode_user
522 self.user_perms = self.user.permissions
522 self.user_perms = self.user.permissions
523 log.debug('checking %s permissions %s for %s %s',
523 log.debug('checking %s permissions %s for %s %s',
524 self.__class__.__name__, self.required_perms, cls,
524 self.__class__.__name__, self.required_perms, cls, self.user)
525 self.user)
526
525
527 if self.check_permissions():
526 if self.check_permissions():
528 log.debug('Permission granted for %s %s' % (cls, self.user))
527 log.debug('Permission granted for %s %s' % (cls, self.user))
@@ -604,6 +603,7 b' class HasRepoPermissionAnyDecorator(Perm'
604 user_perms = set([self.user_perms['repositories'][repo_name]])
603 user_perms = set([self.user_perms['repositories'][repo_name]])
605 except KeyError:
604 except KeyError:
606 return False
605 return False
606
607 if self.required_perms.intersection(user_perms):
607 if self.required_perms.intersection(user_perms):
608 return True
608 return True
609 return False
609 return False
@@ -655,29 +655,37 b' class PermsFunction(object):'
655
655
656 for perm in perms:
656 for perm in perms:
657 if perm not in available_perms:
657 if perm not in available_perms:
658 raise Exception("'%s' permission in not defined" % perm)
658 raise Exception("'%s' permission is not defined" % perm)
659 self.required_perms = set(perms)
659 self.required_perms = set(perms)
660 self.user_perms = None
660 self.user_perms = None
661 self.granted_for = ''
662 self.repo_name = None
661 self.repo_name = None
662 self.group_name = None
663
663
664 def __call__(self, check_Location=''):
664 def __call__(self, check_Location=''):
665 user = request.user
665 user = request.user
666 log.debug('checking %s %s %s', self.__class__.__name__,
666 cls_name = self.__class__.__name__
667 self.required_perms, user)
667 check_scope = {
668 'HasPermissionAll': '',
669 'HasPermissionAny': '',
670 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
671 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
672 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
673 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
674 }.get(cls_name, '?')
675 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
676 self.required_perms, user, check_scope,
677 check_Location or 'unspecified location')
668 if not user:
678 if not user:
669 log.debug('Empty request user')
679 log.debug('Empty request user')
670 return False
680 return False
671 self.user_perms = user.permissions
681 self.user_perms = user.permissions
672 self.granted_for = user
673
674 if self.check_permissions():
682 if self.check_permissions():
675 log.debug('Permission granted %s @ %s', self.granted_for,
683 log.debug('Permission granted for user: %s @ %s', user,
676 check_Location or 'unspecified location')
684 check_Location or 'unspecified location')
677 return True
685 return True
678
686
679 else:
687 else:
680 log.debug('Permission denied for %s @ %s', self.granted_for,
688 log.debug('Permission denied for user: %s @ %s', user,
681 check_Location or 'unspecified location')
689 check_Location or 'unspecified location')
682 return False
690 return False
683
691
@@ -701,7 +709,6 b' class HasPermissionAny(PermsFunction):'
701
709
702
710
703 class HasRepoPermissionAll(PermsFunction):
711 class HasRepoPermissionAll(PermsFunction):
704
705 def __call__(self, repo_name=None, check_Location=''):
712 def __call__(self, repo_name=None, check_Location=''):
706 self.repo_name = repo_name
713 self.repo_name = repo_name
707 return super(HasRepoPermissionAll, self).__call__(check_Location)
714 return super(HasRepoPermissionAll, self).__call__(check_Location)
@@ -711,19 +718,17 b' class HasRepoPermissionAll(PermsFunction'
711 self.repo_name = get_repo_slug(request)
718 self.repo_name = get_repo_slug(request)
712
719
713 try:
720 try:
714 self.user_perms = set(
721 self._user_perms = set(
715 [self.user_perms['repositories'][self.repo_name]]
722 [self.user_perms['repositories'][self.repo_name]]
716 )
723 )
717 except KeyError:
724 except KeyError:
718 return False
725 return False
719 self.granted_for = self.repo_name
726 if self.required_perms.issubset(self._user_perms):
720 if self.required_perms.issubset(self.user_perms):
721 return True
727 return True
722 return False
728 return False
723
729
724
730
725 class HasRepoPermissionAny(PermsFunction):
731 class HasRepoPermissionAny(PermsFunction):
726
727 def __call__(self, repo_name=None, check_Location=''):
732 def __call__(self, repo_name=None, check_Location=''):
728 self.repo_name = repo_name
733 self.repo_name = repo_name
729 return super(HasRepoPermissionAny, self).__call__(check_Location)
734 return super(HasRepoPermissionAny, self).__call__(check_Location)
@@ -733,13 +738,12 b' class HasRepoPermissionAny(PermsFunction'
733 self.repo_name = get_repo_slug(request)
738 self.repo_name = get_repo_slug(request)
734
739
735 try:
740 try:
736 self.user_perms = set(
741 self._user_perms = set(
737 [self.user_perms['repositories'][self.repo_name]]
742 [self.user_perms['repositories'][self.repo_name]]
738 )
743 )
739 except KeyError:
744 except KeyError:
740 return False
745 return False
741 self.granted_for = self.repo_name
746 if self.required_perms.intersection(self._user_perms):
742 if self.required_perms.intersection(self.user_perms):
743 return True
747 return True
744 return False
748 return False
745
749
@@ -751,13 +755,12 b' class HasReposGroupPermissionAny(PermsFu'
751
755
752 def check_permissions(self):
756 def check_permissions(self):
753 try:
757 try:
754 self.user_perms = set(
758 self._user_perms = set(
755 [self.user_perms['repositories_groups'][self.group_name]]
759 [self.user_perms['repositories_groups'][self.group_name]]
756 )
760 )
757 except KeyError:
761 except KeyError:
758 return False
762 return False
759 self.granted_for = self.repo_name
763 if self.required_perms.intersection(self._user_perms):
760 if self.required_perms.intersection(self.user_perms):
761 return True
764 return True
762 return False
765 return False
763
766
@@ -769,13 +772,12 b' class HasReposGroupPermissionAll(PermsFu'
769
772
770 def check_permissions(self):
773 def check_permissions(self):
771 try:
774 try:
772 self.user_perms = set(
775 self._user_perms = set(
773 [self.user_perms['repositories_groups'][self.group_name]]
776 [self.user_perms['repositories_groups'][self.group_name]]
774 )
777 )
775 except KeyError:
778 except KeyError:
776 return False
779 return False
777 self.granted_for = self.repo_name
780 if self.required_perms.issubset(self._user_perms):
778 if self.required_perms.issubset(self.user_perms):
779 return True
781 return True
780 return False
782 return False
781
783
@@ -788,12 +790,16 b' class HasPermissionAnyMiddleware(object)'
788 self.required_perms = set(perms)
790 self.required_perms = set(perms)
789
791
790 def __call__(self, user, repo_name):
792 def __call__(self, user, repo_name):
793 # repo_name MUST be unicode, since we handle keys in permission
794 # dict by unicode
795 repo_name = safe_unicode(repo_name)
791 usr = AuthUser(user.user_id)
796 usr = AuthUser(user.user_id)
792 try:
797 try:
793 self.user_perms = set([usr.permissions['repositories'][repo_name]])
798 self.user_perms = set([usr.permissions['repositories'][repo_name]])
794 except:
799 except Exception:
800 log.error('Exception while accessing permissions %s' %
801 traceback.format_exc())
795 self.user_perms = set()
802 self.user_perms = set()
796 self.granted_for = ''
797 self.username = user.username
803 self.username = user.username
798 self.repo_name = repo_name
804 self.repo_name = repo_name
799 return self.check_permissions()
805 return self.check_permissions()
@@ -803,7 +809,13 b' class HasPermissionAnyMiddleware(object)'
803 'permissions %s for user:%s repository:%s', self.user_perms,
809 'permissions %s for user:%s repository:%s', self.user_perms,
804 self.username, self.repo_name)
810 self.username, self.repo_name)
805 if self.required_perms.intersection(self.user_perms):
811 if self.required_perms.intersection(self.user_perms):
806 log.debug('permission granted')
812 log.debug('permission granted for user:%s on repo:%s' % (
813 self.username, self.repo_name
814 )
815 )
807 return True
816 return True
808 log.debug('permission denied')
817 log.debug('permission denied for user:%s on repo:%s' % (
818 self.username, self.repo_name
819 )
820 )
809 return False
821 return False
@@ -7,6 +7,8 b' import time'
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE
10
12
11 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
12 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
@@ -15,7 +17,7 b' from pylons.templating import render_mak'
15
17
16 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
17
19
18 from rhodecode.lib import str2bool, safe_unicode
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
19 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 HasPermissionAnyMiddleware, CookieStoreWrapper
22 HasPermissionAnyMiddleware, CookieStoreWrapper
21 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
@@ -28,6 +30,22 b' from rhodecode.model.scm import ScmModel'
28 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
29
31
30
32
33 class BasicAuth(AuthBasicAuthenticator):
34
35 def __init__(self, realm, authfunc, auth_http_code=None):
36 self.realm = realm
37 self.authfunc = authfunc
38 self._rc_auth_http_code = auth_http_code
39
40 def build_authentication(self):
41 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
42 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
43 # return 403 if alternative http return code is specified in
44 # RhodeCode config
45 return HTTPForbidden(headers=head)
46 return HTTPUnauthorized(headers=head)
47
48
31 class BaseVCSController(object):
49 class BaseVCSController(object):
32
50
33 def __init__(self, application, config):
51 def __init__(self, application, config):
@@ -36,7 +54,8 b' class BaseVCSController(object):'
36 # base path of repo locations
54 # base path of repo locations
37 self.basepath = self.config['base_path']
55 self.basepath = self.config['base_path']
38 #authenticate this mercurial request using authfunc
56 #authenticate this mercurial request using authfunc
39 self.authenticate = AuthBasicAuthenticator('', authfunc)
57 self.authenticate = BasicAuth('', authfunc,
58 config.get('auth_ret_code'))
40 self.ipaddr = '0.0.0.0'
59 self.ipaddr = '0.0.0.0'
41
60
42 def _handle_request(self, environ, start_response):
61 def _handle_request(self, environ, start_response):
@@ -24,7 +24,7 b' from beaker.exceptions import BeakerExce'
24 from sqlalchemy.orm.interfaces import MapperOption
24 from sqlalchemy.orm.interfaces import MapperOption
25 from sqlalchemy.orm.query import Query
25 from sqlalchemy.orm.query import Query
26 from sqlalchemy.sql import visitors
26 from sqlalchemy.sql import visitors
27 from rhodecode.lib import safe_str
27 from rhodecode.lib.utils2 import safe_str
28
28
29
29
30 class CachingQuery(Query):
30 class CachingQuery(Query):
@@ -36,7 +36,7 b' from decorator import decorator'
36
36
37 from rhodecode.lib.vcs.utils.lazy import LazyProperty
37 from rhodecode.lib.vcs.utils.lazy import LazyProperty
38 from rhodecode import CELERY_ON
38 from rhodecode import CELERY_ON
39 from rhodecode.lib import str2bool, safe_str
39 from rhodecode.lib.utils2 import str2bool, safe_str
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 from rhodecode.model import init_model
41 from rhodecode.model import init_model
42 from rhodecode.model import meta
42 from rhodecode.model import meta
@@ -40,7 +40,7 b' from pylons.i18n.translation import _'
40 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs import get_backend
41
41
42 from rhodecode import CELERY_ON
42 from rhodecode import CELERY_ON
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
43 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 from rhodecode.lib.helpers import person
46 from rhodecode.lib.helpers import person
@@ -147,6 +147,7 b' def get_commits_stats(repo_name, ts_min_'
147 last_rev, last_rev + parse_limit)
147 last_rev, last_rev + parse_limit)
148 )
148 )
149 for cs in repo[last_rev:last_rev + parse_limit]:
149 for cs in repo[last_rev:last_rev + parse_limit]:
150 log.debug('parsing %s' % cs)
150 last_cs = cs # remember last parsed changeset
151 last_cs = cs # remember last parsed changeset
151 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
153 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
@@ -233,10 +234,10 b' def get_commits_stats(repo_name, ts_min_'
233 lock.release()
234 lock.release()
234 return False
235 return False
235
236
236 #final release
237 # final release
237 lock.release()
238 lock.release()
238
239
239 #execute another task if celery is enabled
240 # execute another task if celery is enabled
240 if len(repo.revisions) > 1 and CELERY_ON:
241 if len(repo.revisions) > 1 and CELERY_ON:
241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
242 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
242 return True
243 return True
@@ -327,7 +328,7 b' def send_email(recipients, subject, body'
327 DBS = get_session()
328 DBS = get_session()
328
329
329 email_config = config
330 email_config = config
330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
331 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
331 if not recipients:
332 if not recipients:
332 # if recipients are not defined we send to email_config + all admins
333 # if recipients are not defined we send to email_config + all admins
333 admins = [u.email for u in User.query()
334 admins = [u.email for u in User.query()
@@ -395,6 +396,7 b' def create_repo_fork(form_data, cur_user'
395 DBS.commit()
396 DBS.commit()
396
397
397 def __get_codes_stats(repo_name):
398 def __get_codes_stats(repo_name):
399 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
398 repo = Repository.get_by_repo_name(repo_name).scm_instance
400 repo = Repository.get_by_repo_name(repo_name).scm_instance
399
401
400 tip = repo.get_changeset()
402 tip = repo.get_changeset()
@@ -1,9 +1,9 b''
1 import rhodecode
1 import rhodecode
2 from rhodecode.lib.utils import BasePasterCommand, Command
2 from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions
3 from celery.app import app_or_default
3 from celery.app import app_or_default
4 from celery.bin import camqadm, celerybeat, celeryd, celeryev
4 from celery.bin import camqadm, celerybeat, celeryd, celeryev
5
5
6 from rhodecode.lib import str2bool
6 from rhodecode.lib.utils2 import str2bool
7
7
8 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
8 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
9 'CAMQPAdminCommand', 'CeleryEventCommand']
9 'CAMQPAdminCommand', 'CeleryEventCommand']
@@ -39,9 +39,11 b' class CeleryCommand(BasePasterCommand):'
39 raise Exception('Please enable celery_on in .ini config '
39 raise Exception('Please enable celery_on in .ini config '
40 'file before running celeryd')
40 'file before running celeryd')
41 rhodecode.CELERY_ON = CELERY_ON
41 rhodecode.CELERY_ON = CELERY_ON
42 load_rcextensions(config['here'])
42 cmd = self.celery_command(app_or_default())
43 cmd = self.celery_command(app_or_default())
43 return cmd.run(**vars(self.options))
44 return cmd.run(**vars(self.options))
44
45
46
45 class CeleryDaemonCommand(CeleryCommand):
47 class CeleryDaemonCommand(CeleryCommand):
46 """Start the celery worker
48 """Start the celery worker
47
49
@@ -82,6 +84,7 b' class CAMQPAdminCommand(CeleryCommand):'
82 parser = Command.standard_parser(quiet=True)
84 parser = Command.standard_parser(quiet=True)
83 celery_command = camqadm.AMQPAdminCommand
85 celery_command = camqadm.AMQPAdminCommand
84
86
87
85 class CeleryEventCommand(CeleryCommand):
88 class CeleryEventCommand(CeleryCommand):
86 """Celery event command.
89 """Celery event command.
87
90
@@ -25,16 +25,93 b''
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import datetime
29 import functools
30 import decimal
28 from rhodecode import __platform__, PLATFORM_WIN
31 from rhodecode import __platform__, PLATFORM_WIN
29
32
30 #==============================================================================
33 #==============================================================================
31 # json
34 # json
32 #==============================================================================
35 #==============================================================================
36
37
38 def _is_aware(value):
39 """
40 Determines if a given datetime.time is aware.
41
42 The logic is described in Python's docs:
43 http://docs.python.org/library/datetime.html#datetime.tzinfo
44 """
45 return (value.tzinfo is not None
46 and value.tzinfo.utcoffset(value) is not None)
47
48
49 def _obj_dump(obj):
50 """
51 Custom function for dumping objects to JSON, if obj has __json__ attribute
52 or method defined it will be used for serialization
53
54 :param obj:
55 """
56
57 if isinstance(obj, complex):
58 return [obj.real, obj.imag]
59 # See "Date Time String Format" in the ECMA-262 specification.
60 # some code borrowed from django 1.4
61 elif isinstance(obj, datetime.datetime):
62 r = obj.isoformat()
63 if obj.microsecond:
64 r = r[:23] + r[26:]
65 if r.endswith('+00:00'):
66 r = r[:-6] + 'Z'
67 return r
68 elif isinstance(obj, datetime.date):
69 return obj.isoformat()
70 elif isinstance(obj, decimal.Decimal):
71 return str(obj)
72 elif isinstance(obj, datetime.time):
73 if _is_aware(obj):
74 raise ValueError("JSON can't represent timezone-aware times.")
75 r = obj.isoformat()
76 if obj.microsecond:
77 r = r[:12]
78 return r
79 elif isinstance(obj, set):
80 return list(obj)
81 elif isinstance(obj, OrderedDict):
82 return obj.as_dict()
83 elif hasattr(obj, '__json__'):
84 if callable(obj.__json__):
85 return obj.__json__()
86 else:
87 return obj.__json__
88 else:
89 raise NotImplementedError
90
33 try:
91 try:
34 import json
92 import json
93
94 # extended JSON encoder for json
95 class ExtendedEncoder(json.JSONEncoder):
96 def default(self, obj):
97 try:
98 return _obj_dump(obj)
99 except NotImplementedError:
100 pass
101 return json.JSONEncoder.default(self, obj)
102 # monkey-patch JSON encoder to use extended version
103 json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
35 except ImportError:
104 except ImportError:
36 import simplejson as json
105 import simplejson as json
37
106
107 def extended_encode(obj):
108 try:
109 return _obj_dump(obj)
110 except NotImplementedError:
111 pass
112 raise TypeError("%r is not JSON serializable" % (obj,))
113 json.dumps = functools.partial(json.dumps, default=extended_encode)
114
38
115
39 #==============================================================================
116 #==============================================================================
40 # izip_longest
117 # izip_longest
@@ -44,11 +121,11 b' try:'
44 except ImportError:
121 except ImportError:
45 import itertools
122 import itertools
46
123
47 def izip_longest(*args, **kwds): # noqa
124 def izip_longest(*args, **kwds):
48 fillvalue = kwds.get("fillvalue")
125 fillvalue = kwds.get("fillvalue")
49
126
50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
127 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
51 yield counter() # yields the fillvalue, or raises IndexError
128 yield counter() # yields the fillvalue, or raises IndexError
52
129
53 fillers = itertools.repeat(fillvalue)
130 fillers = itertools.repeat(fillvalue)
54 iters = [itertools.chain(it, sentinel(), fillers)
131 iters = [itertools.chain(it, sentinel(), fillers)
@@ -376,7 +376,7 b' class DbManage(object):'
376
376
377 if not self.tests and not test_repo_path:
377 if not self.tests and not test_repo_path:
378 path = raw_input(
378 path = raw_input(
379 'Enter a valid path to store repositories. '
379 'Enter a valid absolute path to store repositories. '
380 'All repositories in that path will be added automatically:'
380 'All repositories in that path will be added automatically:'
381 )
381 )
382 else:
382 else:
@@ -388,8 +388,12 b' class DbManage(object):'
388 path_ok = False
388 path_ok = False
389 log.error('Given path %s is not a valid directory' % path)
389 log.error('Given path %s is not a valid directory' % path)
390
390
391 elif not os.path.isabs(path):
392 path_ok = False
393 log.error('Given path %s is not an absolute path' % path)
394
391 # check write access
395 # check write access
392 if not os.access(path, os.W_OK) and path_ok:
396 elif not os.access(path, os.W_OK) and path_ok:
393 path_ok = False
397 path_ok = False
394 log.error('No write permission to given path %s' % path)
398 log.error('No write permission to given path %s' % path)
395
399
@@ -71,9 +71,6 b' class InvalidScriptError(ScriptError):'
71 """Invalid script error."""
71 """Invalid script error."""
72
72
73
73
74 class InvalidVersionError(Error):
75 """Invalid version error."""
76
77 # migrate.changeset
74 # migrate.changeset
78
75
79 class NotSupportedError(Error):
76 class NotSupportedError(Error):
@@ -39,7 +39,7 b' from rhodecode.lib.vcs.utils.helpers imp'
39 from rhodecode.lib.vcs.exceptions import VCSError
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 generate_api_key, safe_unicode
43 generate_api_key, safe_unicode
44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 from rhodecode.lib.compat import json
45 from rhodecode.lib.compat import json
@@ -717,7 +717,7 b' class Repository(Base, BaseModel):'
717 return repo
717 return repo
718
718
719
719
720 class RepoGroup(Base, BaseModel):
720 class Group(Base, BaseModel):
721 __tablename__ = 'groups'
721 __tablename__ = 'groups'
722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
@@ -728,8 +728,7 b' class RepoGroup(Base, BaseModel):'
728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
730
730
731 parent_group = relationship('RepoGroup', remote_side=group_id)
731 parent_group = relationship('Group', remote_side=group_id)
732
733
732
734 def __init__(self, group_name='', parent_group=None):
733 def __init__(self, group_name='', parent_group=None):
735 self.group_name = group_name
734 self.group_name = group_name
@@ -39,12 +39,20 b' from webhelpers.html.tags import _set_in'
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 get_changeset_safe
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.markup_renderer import MarkupRenderer
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
49 def shorter(text, size=20):
50 postfix = '...'
51 if len(text) > size:
52 return text[:size - len(postfix)] + postfix
53 return text
54
55
48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
56 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 """
57 """
50 Reset button
58 Reset button
@@ -67,7 +75,7 b' def FID(raw_id, path):'
67 :param path:
75 :param path:
68 """
76 """
69
77
70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
78 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
71
79
72
80
73 def get_token():
81 def get_token():
@@ -86,6 +94,7 b' def get_token():'
86 session.save()
94 session.save()
87 return session[token_key]
95 return session[token_key]
88
96
97
89 class _GetError(object):
98 class _GetError(object):
90 """Get error from form_errors, and represent it as span wrapped error
99 """Get error from form_errors, and represent it as span wrapped error
91 message
100 message
@@ -101,6 +110,7 b' class _GetError(object):'
101
110
102 get_error = _GetError()
111 get_error = _GetError()
103
112
113
104 class _ToolTip(object):
114 class _ToolTip(object):
105
115
106 def __call__(self, tooltip_title, trim_at=50):
116 def __call__(self, tooltip_title, trim_at=50):
@@ -112,6 +122,7 b' class _ToolTip(object):'
112 return escape(tooltip_title)
122 return escape(tooltip_title)
113 tooltip = _ToolTip()
123 tooltip = _ToolTip()
114
124
125
115 class _FilesBreadCrumbs(object):
126 class _FilesBreadCrumbs(object):
116
127
117 def __call__(self, repo_name, rev, paths):
128 def __call__(self, repo_name, rev, paths):
@@ -136,8 +147,10 b' class _FilesBreadCrumbs(object):'
136
147
137 files_breadcrumbs = _FilesBreadCrumbs()
148 files_breadcrumbs = _FilesBreadCrumbs()
138
149
150
139 class CodeHtmlFormatter(HtmlFormatter):
151 class CodeHtmlFormatter(HtmlFormatter):
140 """My code Html Formatter for source codes
152 """
153 My code Html Formatter for source codes
141 """
154 """
142
155
143 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
@@ -319,7 +332,7 b' flash = _Flash()'
319 # SCM FILTERS available via h.
332 # SCM FILTERS available via h.
320 #==============================================================================
333 #==============================================================================
321 from rhodecode.lib.vcs.utils import author_name, author_email
334 from rhodecode.lib.vcs.utils import author_name, author_email
322 from rhodecode.lib import credentials_filter, age as _age
335 from rhodecode.lib.utils2 import credentials_filter, age as _age
323 from rhodecode.model.db import User
336 from rhodecode.model.db import User
324
337
325 age = lambda x: _age(x)
338 age = lambda x: _age(x)
@@ -759,10 +772,10 b' def fancy_file_stats(stats):'
759 d_v = d if d > 0 else ''
772 d_v = d if d > 0 else ''
760
773
761 def cgen(l_type):
774 def cgen(l_type):
762 mapping = {'tr': 'top-right-rounded-corner',
775 mapping = {'tr': 'top-right-rounded-corner-mid',
763 'tl': 'top-left-rounded-corner',
776 'tl': 'top-left-rounded-corner-mid',
764 'br': 'bottom-right-rounded-corner',
777 'br': 'bottom-right-rounded-corner-mid',
765 'bl': 'bottom-left-rounded-corner'}
778 'bl': 'bottom-left-rounded-corner-mid'}
766 map_getter = lambda x: mapping[x]
779 map_getter = lambda x: mapping[x]
767
780
768 if l_type == 'a' and d_v:
781 if l_type == 'a' and d_v:
@@ -801,6 +814,12 b' def urlify_text(text_):'
801
814
802
815
803 def urlify_changesets(text_, repository):
816 def urlify_changesets(text_, repository):
817 """
818 Extract revision ids from changeset and make link from them
819
820 :param text_:
821 :param repository:
822 """
804 import re
823 import re
805 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
824 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
806
825
@@ -839,8 +858,8 b' def urlify_commit(text_, repository=None'
839 import re
858 import re
840 import traceback
859 import traceback
841
860
842 # urlify changesets
861 def escaper(string):
843 text_ = urlify_changesets(text_, repository)
862 return string.replace('<', '&lt;').replace('>', '&gt;')
844
863
845 def linkify_others(t, l):
864 def linkify_others(t, l):
846 urls = re.compile(r'(\<a.*?\<\/a\>)',)
865 urls = re.compile(r'(\<a.*?\<\/a\>)',)
@@ -852,6 +871,11 b' def urlify_commit(text_, repository=None'
852 links.append(e)
871 links.append(e)
853
872
854 return ''.join(links)
873 return ''.join(links)
874
875
876 # urlify changesets - extrac revisions and make link out of them
877 text_ = urlify_changesets(escaper(text_), repository)
878
855 try:
879 try:
856 conf = config['app_conf']
880 conf = config['app_conf']
857
881
@@ -27,9 +27,10 b' import sys'
27
27
28 from mercurial.scmutil import revrange
28 from mercurial.scmutil import revrange
29 from mercurial.node import nullrev
29 from mercurial.node import nullrev
30
30 from rhodecode import EXTENSIONS
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.utils import action_logger
32 from rhodecode.lib.utils import action_logger
33 from inspect import isfunction
33
34
34
35
35 def repo_size(ui, repo, hooktype=None, **kwargs):
36 def repo_size(ui, repo, hooktype=None, **kwargs):
@@ -78,14 +79,19 b' def log_pull_action(ui, repo, **kwargs):'
78 :param repo:
79 :param repo:
79 """
80 """
80
81
81 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
82 extras = dict(repo.ui.configitems('rhodecode_extras'))
82 username = extra_params['username']
83 username = extras['username']
83 repository = extra_params['repository']
84 repository = extras['repository']
84 action = 'pull'
85 action = 'pull'
85
86
86 action_logger(username, action, repository, extra_params['ip'],
87 action_logger(username, action, repository, extras['ip'], commit=True)
87 commit=True)
88 # extension hook call
89 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
88
90
91 if isfunction(callback):
92 kw = {}
93 kw.update(extras)
94 callback(**kw)
89 return 0
95 return 0
90
96
91
97
@@ -97,10 +103,10 b' def log_push_action(ui, repo, **kwargs):'
97 :param repo:
103 :param repo:
98 """
104 """
99
105
100 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
106 extras = dict(repo.ui.configitems('rhodecode_extras'))
101 username = extra_params['username']
107 username = extras['username']
102 repository = extra_params['repository']
108 repository = extras['repository']
103 action = extra_params['action'] + ':%s'
109 action = extras['action'] + ':%s'
104 node = kwargs['node']
110 node = kwargs['node']
105
111
106 def get_revs(repo, rev_opt):
112 def get_revs(repo, rev_opt):
@@ -119,16 +125,22 b' def log_push_action(ui, repo, **kwargs):'
119
125
120 action = action % ','.join(revs)
126 action = action % ','.join(revs)
121
127
122 action_logger(username, action, repository, extra_params['ip'],
128 action_logger(username, action, repository, extras['ip'], commit=True)
123 commit=True)
124
129
130 # extension hook call
131 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
132 if isfunction(callback):
133 kw = {'pushed_revs': revs}
134 kw.update(extras)
135 callback(**kw)
125 return 0
136 return 0
126
137
127
138
128 def log_create_repository(repository_dict, created_by, **kwargs):
139 def log_create_repository(repository_dict, created_by, **kwargs):
129 """
140 """
130 Post create repository Hook. This is a dummy function for admins to re-use
141 Post create repository Hook. This is a dummy function for admins to re-use
131 if needed
142 if needed. It's taken from rhodecode-extensions module and executed
143 if present
132
144
133 :param repository: dict dump of repository object
145 :param repository: dict dump of repository object
134 :param created_by: username who created repository
146 :param created_by: username who created repository
@@ -151,5 +163,12 b' def log_create_repository(repository_dic'
151
163
152 """
164 """
153
165
166 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
167 if isfunction(callback):
168 kw = {}
169 kw.update(repository_dict)
170 kw.update({'created_by': created_by})
171 kw.update(kwargs)
172 return callback(**kw)
154
173
155 return 0
174 return 0
@@ -25,6 +25,7 b''
25 import os
25 import os
26 import sys
26 import sys
27 import traceback
27 import traceback
28 import logging
28 from os.path import dirname as dn, join as jn
29 from os.path import dirname as dn, join as jn
29
30
30 #to get the rhodecode import
31 #to get the rhodecode import
@@ -46,11 +47,9 b' from rhodecode.model import init_model'
46 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.config.environment import load_environment
49 from rhodecode.config.environment import load_environment
49 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty
50 from rhodecode.lib.utils2 import LazyProperty
50 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
51
52 load_rcextensions
52 # EXTENSIONS WE WANT TO INDEX CONTENT OFF
53 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
54
53
55 # CUSTOM ANALYZER wordsplit + lowercase filter
54 # CUSTOM ANALYZER wordsplit + lowercase filter
56 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
55 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
@@ -84,18 +83,17 b' class MakeIndex(BasePasterCommand):'
84 parser = Command.standard_parser(verbose=True)
83 parser = Command.standard_parser(verbose=True)
85
84
86 def command(self):
85 def command(self):
87
86 logging.config.fileConfig(self.path_to_ini_file)
88 from pylons import config
87 from pylons import config
89 add_cache(config)
88 add_cache(config)
90 engine = engine_from_config(config, 'sqlalchemy.db1.')
89 engine = engine_from_config(config, 'sqlalchemy.db1.')
91 init_model(engine)
90 init_model(engine)
92
93 index_location = config['index_dir']
91 index_location = config['index_dir']
94 repo_location = self.options.repo_location \
92 repo_location = self.options.repo_location \
95 if self.options.repo_location else RepoModel().repos_path
93 if self.options.repo_location else RepoModel().repos_path
96 repo_list = map(strip, self.options.repo_list.split(',')) \
94 repo_list = map(strip, self.options.repo_list.split(',')) \
97 if self.options.repo_list else None
95 if self.options.repo_list else None
98
96 load_rcextensions(config['here'])
99 #======================================================================
97 #======================================================================
100 # WHOOSH DAEMON
98 # WHOOSH DAEMON
101 #======================================================================
99 #======================================================================
@@ -105,7 +103,7 b' class MakeIndex(BasePasterCommand):'
105 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
103 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
106 WhooshIndexingDaemon(index_location=index_location,
104 WhooshIndexingDaemon(index_location=index_location,
107 repo_location=repo_location,
105 repo_location=repo_location,
108 repo_list=repo_list)\
106 repo_list=repo_list,)\
109 .run(full_index=self.options.full_index)
107 .run(full_index=self.options.full_index)
110 l.release()
108 l.release()
111 except LockHeld:
109 except LockHeld:
@@ -38,34 +38,17 b' from os.path import join as jn'
38 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
38 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
39 sys.path.append(project_path)
39 sys.path.append(project_path)
40
40
41
41 from rhodecode.config.conf import INDEX_EXTENSIONS
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43 from rhodecode.lib import safe_unicode
43 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
44 from rhodecode.lib.indexers import SCHEMA, IDX_NAME
45
45
46 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
46 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
47 NodeDoesNotExistError
47 NodeDoesNotExistError
48
48
49 from whoosh.index import create_in, open_dir
49 from whoosh.index import create_in, open_dir
50
50
51
51 log = logging.getLogger('whoosh_indexer')
52 log = logging.getLogger('whooshIndexer')
53 # create logger
54 log.setLevel(logging.DEBUG)
55 log.propagate = False
56 # create console handler and set level to debug
57 ch = logging.StreamHandler()
58 ch.setLevel(logging.DEBUG)
59
60 # create formatter
61 formatter = logging.Formatter("%(asctime)s - %(name)s -"
62 " %(levelname)s - %(message)s")
63
64 # add formatter to ch
65 ch.setFormatter(formatter)
66
67 # add ch to logger
68 log.addHandler(ch)
69
52
70
53
71 class WhooshIndexingDaemon(object):
54 class WhooshIndexingDaemon(object):
@@ -103,7 +86,8 b' class WhooshIndexingDaemon(object):'
103 self.initial = True
86 self.initial = True
104
87
105 def get_paths(self, repo):
88 def get_paths(self, repo):
106 """recursive walk in root dir and return a set of all path in that dir
89 """
90 recursive walk in root dir and return a set of all path in that dir
107 based on repository walk function
91 based on repository walk function
108 """
92 """
109 index_paths_ = set()
93 index_paths_ = set()
@@ -127,32 +111,39 b' class WhooshIndexingDaemon(object):'
127 return mktime(node.last_changeset.date.timetuple())
111 return mktime(node.last_changeset.date.timetuple())
128
112
129 def add_doc(self, writer, path, repo, repo_name):
113 def add_doc(self, writer, path, repo, repo_name):
130 """Adding doc to writer this function itself fetches data from
114 """
131 the instance of vcs backend"""
115 Adding doc to writer this function itself fetches data from
132 node = self.get_node(repo, path)
116 the instance of vcs backend
117 """
133
118
134 #we just index the content of chosen files, and skip binary files
119 node = self.get_node(repo, path)
120 indexed = indexed_w_content = 0
121 # we just index the content of chosen files, and skip binary files
135 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
122 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
136
137 u_content = node.content
123 u_content = node.content
138 if not isinstance(u_content, unicode):
124 if not isinstance(u_content, unicode):
139 log.warning(' >> %s Could not get this content as unicode '
125 log.warning(' >> %s Could not get this content as unicode '
140 'replacing with empty content', path)
126 'replacing with empty content' % path)
141 u_content = u''
127 u_content = u''
142 else:
128 else:
143 log.debug(' >> %s [WITH CONTENT]' % path)
129 log.debug(' >> %s [WITH CONTENT]' % path)
130 indexed_w_content += 1
144
131
145 else:
132 else:
146 log.debug(' >> %s' % path)
133 log.debug(' >> %s' % path)
147 #just index file name without it's content
134 # just index file name without it's content
148 u_content = u''
135 u_content = u''
136 indexed += 1
149
137
150 writer.add_document(owner=unicode(repo.contact),
138 writer.add_document(
151 repository=safe_unicode(repo_name),
139 owner=unicode(repo.contact),
152 path=safe_unicode(path),
140 repository=safe_unicode(repo_name),
153 content=u_content,
141 path=safe_unicode(path),
154 modtime=self.get_node_mtime(node),
142 content=u_content,
155 extension=node.extension)
143 modtime=self.get_node_mtime(node),
144 extension=node.extension
145 )
146 return indexed, indexed_w_content
156
147
157 def build_index(self):
148 def build_index(self):
158 if os.path.exists(self.index_location):
149 if os.path.exists(self.index_location):
@@ -164,19 +155,25 b' class WhooshIndexingDaemon(object):'
164
155
165 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
156 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
166 writer = idx.writer()
157 writer = idx.writer()
167
158 log.debug('BUILDIN INDEX FOR EXTENSIONS %s' % INDEX_EXTENSIONS)
168 for repo_name, repo in self.repo_paths.items():
159 for repo_name, repo in self.repo_paths.items():
169 log.debug('building index @ %s' % repo.path)
160 log.debug('building index @ %s' % repo.path)
170
161 i_cnt = iwc_cnt = 0
171 for idx_path in self.get_paths(repo):
162 for idx_path in self.get_paths(repo):
172 self.add_doc(writer, idx_path, repo, repo_name)
163 i, iwc = self.add_doc(writer, idx_path, repo, repo_name)
164 i_cnt += i
165 iwc_cnt += iwc
166 log.debug('added %s files %s with content for repo %s' % (
167 i_cnt + iwc_cnt, iwc_cnt, repo.path)
168 )
173
169
174 log.debug('>> COMMITING CHANGES <<')
170 log.debug('>> COMMITING CHANGES <<')
175 writer.commit(merge=True)
171 writer.commit(merge=True)
176 log.debug('>>> FINISHED BUILDING INDEX <<<')
172 log.debug('>>> FINISHED BUILDING INDEX <<<')
177
173
178 def update_index(self):
174 def update_index(self):
179 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
175 log.debug('STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s' %
176 INDEX_EXTENSIONS)
180
177
181 idx = open_dir(self.index_location, indexname=self.indexname)
178 idx = open_dir(self.index_location, indexname=self.indexname)
182 # The set of all paths in the index
179 # The set of all paths in the index
@@ -215,14 +212,19 b' class WhooshIndexingDaemon(object):'
215 # Loop over the files in the filesystem
212 # Loop over the files in the filesystem
216 # Assume we have a function that gathers the filenames of the
213 # Assume we have a function that gathers the filenames of the
217 # documents to be indexed
214 # documents to be indexed
215 ri_cnt = riwc_cnt = 0
218 for repo_name, repo in self.repo_paths.items():
216 for repo_name, repo in self.repo_paths.items():
219 for path in self.get_paths(repo):
217 for path in self.get_paths(repo):
220 if path in to_index or path not in indexed_paths:
218 if path in to_index or path not in indexed_paths:
221 # This is either a file that's changed, or a new file
219 # This is either a file that's changed, or a new file
222 # that wasn't indexed before. So index it!
220 # that wasn't indexed before. So index it!
223 self.add_doc(writer, path, repo, repo_name)
221 i, iwc = self.add_doc(writer, path, repo, repo_name)
224 log.debug('re indexing %s' % path)
222 log.debug('re indexing %s' % path)
225
223 ri_cnt += i
224 riwc_cnt += iwc
225 log.debug('added %s files %s with content for repo %s' % (
226 ri_cnt + riwc_cnt, riwc_cnt, repo.path)
227 )
226 log.debug('>> COMMITING CHANGES <<')
228 log.debug('>> COMMITING CHANGES <<')
227 writer.commit(merge=True)
229 writer.commit(merge=True)
228 log.debug('>>> FINISHED REBUILDING INDEX <<<')
230 log.debug('>>> FINISHED REBUILDING INDEX <<<')
@@ -27,7 +27,7 b''
27 import re
27 import re
28 import logging
28 import logging
29
29
30 from rhodecode.lib import safe_unicode
30 from rhodecode.lib.utils2 import safe_unicode
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
@@ -23,7 +23,7 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 from rhodecode.lib import str2bool
26 from rhodecode.lib.utils2 import str2bool
27
27
28
28
29 class HttpsFixup(object):
29 class HttpsFixup(object):
@@ -65,11 +65,11 b' dulserver.DEFAULT_HANDLERS = {'
65 }
65 }
66
66
67 from dulwich.repo import Repo
67 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
68 from dulwich.web import make_wsgi_chain
69
69
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib.utils2 import safe_str
73 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.base import BaseVCSController
74 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.auth import get_container_username
75 from rhodecode.lib.utils import is_valid_repo
75 from rhodecode.lib.utils import is_valid_repo
@@ -86,7 +86,9 b" GIT_PROTO_PAT = re.compile(r'^/(.+)/(inf"
86 def is_git(environ):
86 def is_git(environ):
87 path_info = environ['PATH_INFO']
87 path_info = environ['PATH_INFO']
88 isgit_path = GIT_PROTO_PAT.match(path_info)
88 isgit_path = GIT_PROTO_PAT.match(path_info)
89 log.debug('is a git path %s pathinfo : %s' % (isgit_path, path_info))
89 log.debug('pathinfo: %s detected as GIT %s' % (
90 path_info, isgit_path != None)
91 )
90 return isgit_path
92 return isgit_path
91
93
92
94
@@ -113,6 +115,10 b' class SimpleGit(BaseVCSController):'
113 except:
115 except:
114 return HTTPInternalServerError()(environ, start_response)
116 return HTTPInternalServerError()(environ, start_response)
115
117
118 # quick check if that dir exists...
119 if is_valid_repo(repo_name, self.basepath) is False:
120 return HTTPNotFound()(environ, start_response)
121
116 #======================================================================
122 #======================================================================
117 # GET ACTION PULL or PUSH
123 # GET ACTION PULL or PUSH
118 #======================================================================
124 #======================================================================
@@ -121,7 +127,6 b' class SimpleGit(BaseVCSController):'
121 #======================================================================
127 #======================================================================
122 # CHECK ANONYMOUS PERMISSION
128 # CHECK ANONYMOUS PERMISSION
123 #======================================================================
129 #======================================================================
124
125 if action in ['pull', 'push']:
130 if action in ['pull', 'push']:
126 anonymous_user = self.__get_user('default')
131 anonymous_user = self.__get_user('default')
127 username = anonymous_user.username
132 username = anonymous_user.username
@@ -177,13 +182,9 b' class SimpleGit(BaseVCSController):'
177 #===================================================================
182 #===================================================================
178 # GIT REQUEST HANDLING
183 # GIT REQUEST HANDLING
179 #===================================================================
184 #===================================================================
180 repo_path = safe_str(os.path.join(self.basepath, repo_name))
185 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
181 log.debug('Repository path is %s' % repo_path)
186 log.debug('Repository path is %s' % repo_path)
182
187
183 # quick check if that dir exists...
184 if is_valid_repo(repo_name, self.basepath) is False:
185 return HTTPNotFound()(environ, start_response)
186
187 try:
188 try:
188 #invalidate cache on push
189 #invalidate cache on push
189 if action == 'push':
190 if action == 'push':
@@ -204,7 +205,7 b' class SimpleGit(BaseVCSController):'
204 """
205 """
205 _d = {'/' + repo_name: Repo(repo_path)}
206 _d = {'/' + repo_name: Repo(repo_path)}
206 backend = dulserver.DictBackend(_d)
207 backend = dulserver.DictBackend(_d)
207 gitserve = HTTPGitApplication(backend)
208 gitserve = make_wsgi_chain(backend)
208
209
209 return gitserve
210 return gitserve
210
211
@@ -27,13 +27,14 b''
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30
31
31 from mercurial.error import RepoError
32 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
33
34
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35
36
36 from rhodecode.lib import safe_str
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
@@ -45,13 +46,21 b' log = logging.getLogger(__name__)'
45
46
46
47
47 def is_mercurial(environ):
48 def is_mercurial(environ):
48 """Returns True if request's target is mercurial server - header
49 """
50 Returns True if request's target is mercurial server - header
49 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 """
52 """
51 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
52 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
53 return True
56 ishg_path = True
54 return False
57 else:
58 ishg_path = False
59
60 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
62 )
63 return ishg_path
55
64
56
65
57 class SimpleHg(BaseVCSController):
66 class SimpleHg(BaseVCSController):
@@ -76,16 +85,20 b' class SimpleHg(BaseVCSController):'
76 except:
85 except:
77 return HTTPInternalServerError()(environ, start_response)
86 return HTTPInternalServerError()(environ, start_response)
78
87
88 # quick check if that dir exists...
89 if is_valid_repo(repo_name, self.basepath) is False:
90 return HTTPNotFound()(environ, start_response)
91
79 #======================================================================
92 #======================================================================
80 # GET ACTION PULL or PUSH
93 # GET ACTION PULL or PUSH
81 #======================================================================
94 #======================================================================
82 action = self.__get_action(environ)
95 action = self.__get_action(environ)
96
83 #======================================================================
97 #======================================================================
84 # CHECK ANONYMOUS PERMISSION
98 # CHECK ANONYMOUS PERMISSION
85 #======================================================================
99 #======================================================================
86 if action in ['pull', 'push']:
100 if action in ['pull', 'push']:
87 anonymous_user = self.__get_user('default')
101 anonymous_user = self.__get_user('default')
88
89 username = anonymous_user.username
102 username = anonymous_user.username
90 anonymous_perm = self._check_permission(action, anonymous_user,
103 anonymous_perm = self._check_permission(action, anonymous_user,
91 repo_name)
104 repo_name)
@@ -132,30 +145,28 b' class SimpleHg(BaseVCSController):'
132 start_response)
145 start_response)
133
146
134 #check permissions for this repository
147 #check permissions for this repository
135 perm = self._check_permission(action, user,
148 perm = self._check_permission(action, user, repo_name)
136 repo_name)
137 if perm is not True:
149 if perm is not True:
138 return HTTPForbidden()(environ, start_response)
150 return HTTPForbidden()(environ, start_response)
139
151
140 extras = {'ip': ipaddr,
152 # extras are injected into mercurial UI object and later available
141 'username': username,
153 # in hg hooks executed by rhodecode
142 'action': action,
154 extras = {
143 'repository': repo_name}
155 'ip': ipaddr,
156 'username': username,
157 'action': action,
158 'repository': repo_name
159 }
144
160
145 #======================================================================
161 #======================================================================
146 # MERCURIAL REQUEST HANDLING
162 # MERCURIAL REQUEST HANDLING
147 #======================================================================
163 #======================================================================
148
164 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
149 repo_path = safe_str(os.path.join(self.basepath, repo_name))
150 log.debug('Repository path is %s' % repo_path)
165 log.debug('Repository path is %s' % repo_path)
151
166
152 baseui = make_ui('db')
167 baseui = make_ui('db')
153 self.__inject_extras(repo_path, baseui, extras)
168 self.__inject_extras(repo_path, baseui, extras)
154
169
155 # quick check if that dir exists...
156 if is_valid_repo(repo_name, self.basepath) is False:
157 return HTTPNotFound()(environ, start_response)
158
159 try:
170 try:
160 # invalidate cache on push
171 # invalidate cache on push
161 if action == 'push':
172 if action == 'push':
@@ -51,9 +51,12 b' from rhodecode.lib.caching_query import '
51
51
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
55 CacheInvalidation
55 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
56 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.vcs.utils.fakemod import create_module
57
60
58 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
59
62
@@ -61,7 +64,8 b" REMOVED_REPO_PAT = re.compile(r'rm__\\d{8"
61
64
62
65
63 def recursive_replace(str_, replace=' '):
66 def recursive_replace(str_, replace=' '):
64 """Recursive replace of given sign to just one instance
67 """
68 Recursive replace of given sign to just one instance
65
69
66 :param str_: given string
70 :param str_: given string
67 :param replace: char to find and replace multiple instances
71 :param replace: char to find and replace multiple instances
@@ -79,7 +83,8 b" def recursive_replace(str_, replace=' ')"
79
83
80
84
81 def repo_name_slug(value):
85 def repo_name_slug(value):
82 """Return slug of name of repository
86 """
87 Return slug of name of repository
83 This function is called on each creation/modification
88 This function is called on each creation/modification
84 of repository to prevent bad names in repo
89 of repository to prevent bad names in repo
85 """
90 """
@@ -154,7 +159,10 b' def action_logger(user, action, repo, ip'
154 user_log.user_ip = ipaddr
159 user_log.user_ip = ipaddr
155 sa.add(user_log)
160 sa.add(user_log)
156
161
157 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
162 log.info(
163 'Adding user %s, action %s on %s' % (user_obj, action,
164 safe_unicode(repo))
165 )
158 if commit:
166 if commit:
159 sa.commit()
167 sa.commit()
160 except:
168 except:
@@ -198,12 +206,13 b' def get_repos(path, recursive=False):'
198 def is_valid_repo(repo_name, base_path):
206 def is_valid_repo(repo_name, base_path):
199 """
207 """
200 Returns True if given path is a valid repository False otherwise
208 Returns True if given path is a valid repository False otherwise
209
201 :param repo_name:
210 :param repo_name:
202 :param base_path:
211 :param base_path:
203
212
204 :return True: if given path is a valid repository
213 :return True: if given path is a valid repository
205 """
214 """
206 full_path = os.path.join(base_path, repo_name)
215 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
207
216
208 try:
217 try:
209 get_scm(full_path)
218 get_scm(full_path)
@@ -219,7 +228,7 b' def is_valid_repos_group(repos_group_nam'
219 :param repo_name:
228 :param repo_name:
220 :param base_path:
229 :param base_path:
221 """
230 """
222 full_path = os.path.join(base_path, repos_group_name)
231 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
223
232
224 # check if it's not a repo
233 # check if it's not a repo
225 if is_valid_repo(repos_group_name, base_path):
234 if is_valid_repo(repos_group_name, base_path):
@@ -258,7 +267,8 b" ui_sections = ['alias', 'auth',"
258
267
259
268
260 def make_ui(read_from='file', path=None, checkpaths=True):
269 def make_ui(read_from='file', path=None, checkpaths=True):
261 """A function that will read python rc files or database
270 """
271 A function that will read python rc files or database
262 and make an mercurial ui object from read options
272 and make an mercurial ui object from read options
263
273
264 :param path: path to mercurial config file
274 :param path: path to mercurial config file
@@ -371,15 +381,16 b' class EmptyChangeset(BaseChangeset):'
371 return 0
381 return 0
372
382
373
383
374 def map_groups(groups):
384 def map_groups(path):
375 """
385 """
376 Checks for groups existence, and creates groups structures.
386 Given a full path to a repository, create all nested groups that this
377 It returns last group in structure
387 repo is inside. This function creates parent-child relationships between
388 groups and creates default perms for all new groups.
378
389
379 :param groups: list of groups structure
390 :param paths: full path to repository
380 """
391 """
381 sa = meta.Session
392 sa = meta.Session
382
393 groups = path.split(Repository.url_sep())
383 parent = None
394 parent = None
384 group = None
395 group = None
385
396
@@ -391,22 +402,18 b' def map_groups(groups):'
391 group = RepoGroup.get_by_group_name(group_name)
402 group = RepoGroup.get_by_group_name(group_name)
392 desc = '%s group' % group_name
403 desc = '%s group' % group_name
393
404
394 # # WTF that doesn't work !?
395 # if group is None:
396 # group = rgm.create(group_name, desc, parent, just_db=True)
397 # sa.commit()
398
399 # skip folders that are now removed repos
405 # skip folders that are now removed repos
400 if REMOVED_REPO_PAT.match(group_name):
406 if REMOVED_REPO_PAT.match(group_name):
401 break
407 break
402
408
403 if group is None:
409 if group is None:
404 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
410 log.debug('creating group level: %s group_name: %s' % (lvl,
411 group_name))
405 group = RepoGroup(group_name, parent)
412 group = RepoGroup(group_name, parent)
406 group.group_description = desc
413 group.group_description = desc
407 sa.add(group)
414 sa.add(group)
408 rgm._create_default_perms(group)
415 rgm._create_default_perms(group)
409 sa.commit()
416 sa.flush()
410 parent = group
417 parent = group
411 return group
418 return group
412
419
@@ -429,7 +436,7 b' def repo2db_mapper(initial_repo_list, re'
429 added = []
436 added = []
430
437
431 for name, repo in initial_repo_list.items():
438 for name, repo in initial_repo_list.items():
432 group = map_groups(name.split(Repository.url_sep()))
439 group = map_groups(name)
433 if not rm.get_by_repo_name(name, cache=False):
440 if not rm.get_by_repo_name(name, cache=False):
434 log.info('repository %s not found creating default' % name)
441 log.info('repository %s not found creating default' % name)
435 added.append(name)
442 added.append(name)
@@ -446,13 +453,19 b' def repo2db_mapper(initial_repo_list, re'
446 sa.commit()
453 sa.commit()
447 removed = []
454 removed = []
448 if remove_obsolete:
455 if remove_obsolete:
449 #remove from database those repositories that are not in the filesystem
456 # remove from database those repositories that are not in the filesystem
450 for repo in sa.query(Repository).all():
457 for repo in sa.query(Repository).all():
451 if repo.repo_name not in initial_repo_list.keys():
458 if repo.repo_name not in initial_repo_list.keys():
459 log.debug("Removing non existing repository found in db %s" %
460 repo.repo_name)
452 removed.append(repo.repo_name)
461 removed.append(repo.repo_name)
453 sa.delete(repo)
462 sa.delete(repo)
454 sa.commit()
463 sa.commit()
455
464
465 # clear cache keys
466 log.debug("Clearing cache keys now...")
467 CacheInvalidation.clear_cache()
468 sa.commit()
456 return added, removed
469 return added, removed
457
470
458
471
@@ -484,6 +497,30 b' def add_cache(settings):'
484 beaker.cache.cache_regions[region] = region_settings
497 beaker.cache.cache_regions[region] = region_settings
485
498
486
499
500 def load_rcextensions(root_path):
501 import rhodecode
502 from rhodecode.config import conf
503
504 path = os.path.join(root_path, 'rcextensions', '__init__.py')
505 if os.path.isfile(path):
506 rcext = create_module('rc', path)
507 EXT = rhodecode.EXTENSIONS = rcext
508 log.debug('Found rcextensions now loading %s...' % rcext)
509
510 # Additional mappings that are not present in the pygments lexers
511 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
512
513 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
514
515 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
516 log.debug('settings custom INDEX_EXTENSIONS')
517 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
518
519 #ADDITIONAL MAPPINGS
520 log.debug('adding extra into INDEX_EXTENSIONS')
521 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
522
523
487 #==============================================================================
524 #==============================================================================
488 # TEST FUNCTIONS AND CREATORS
525 # TEST FUNCTIONS AND CREATORS
489 #==============================================================================
526 #==============================================================================
@@ -624,6 +661,6 b' class BasePasterCommand(Command):'
624 """
661 """
625 from pylons import config as pylonsconfig
662 from pylons import config as pylonsconfig
626
663
627 path_to_ini_file = os.path.realpath(conf)
664 self.path_to_ini_file = os.path.realpath(conf)
628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
665 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
629 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
666 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -68,19 +68,24 b' class GitChangeset(BaseChangeset):'
68 def branch(self):
68 def branch(self):
69 # TODO: Cache as we walk (id <-> branch name mapping)
69 # TODO: Cache as we walk (id <-> branch name mapping)
70 refs = self.repository._repo.get_refs()
70 refs = self.repository._repo.get_refs()
71 heads = [(key[len('refs/heads/'):], val) for key, val in refs.items()
71 heads = {}
72 if key.startswith('refs/heads/')]
72 for key, val in refs.items():
73 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
74 if key.startswith(ref_key):
75 n = key[len(ref_key):]
76 if n not in ['HEAD']:
77 heads[n] = val
73
78
74 for name, id in heads:
79 for name, id in heads.iteritems():
75 walker = self.repository._repo.object_store.get_graph_walker([id])
80 walker = self.repository._repo.object_store.get_graph_walker([id])
76 while True:
81 while True:
77 id = walker.next()
82 id_ = walker.next()
78 if not id:
83 if not id_:
79 break
84 break
80 if id == self.id:
85 if id_ == self.id:
81 return safe_unicode(name)
86 return safe_unicode(name)
82 raise ChangesetError("This should not happen... Have you manually "
87 raise ChangesetError("This should not happen... Have you manually "
83 "change id of the changeset?")
88 "change id of the changeset?")
84
89
85 def _fix_path(self, path):
90 def _fix_path(self, path):
86 """
91 """
@@ -92,6 +97,7 b' class GitChangeset(BaseChangeset):'
92 return path
97 return path
93
98
94 def _get_id_for_path(self, path):
99 def _get_id_for_path(self, path):
100
95 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
101 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
96 if not path in self._paths:
102 if not path in self._paths:
97 path = path.strip('/')
103 path = path.strip('/')
@@ -103,24 +109,23 b' class GitChangeset(BaseChangeset):'
103 splitted = path.split('/')
109 splitted = path.split('/')
104 dirs, name = splitted[:-1], splitted[-1]
110 dirs, name = splitted[:-1], splitted[-1]
105 curdir = ''
111 curdir = ''
112
113 # initially extract things from root dir
114 for item, stat, id in tree.iteritems():
115 if curdir:
116 name = '/'.join((curdir, item))
117 else:
118 name = item
119 self._paths[name] = id
120 self._stat_modes[name] = stat
121
106 for dir in dirs:
122 for dir in dirs:
107 if curdir:
123 if curdir:
108 curdir = '/'.join((curdir, dir))
124 curdir = '/'.join((curdir, dir))
109 else:
125 else:
110 curdir = dir
126 curdir = dir
111 #if curdir in self._paths:
112 ## This path have been already traversed
113 ## Update tree and continue
114 #tree = self.repository._repo[self._paths[curdir]]
115 #continue
116 dir_id = None
127 dir_id = None
117 for item, stat, id in tree.iteritems():
128 for item, stat, id in tree.iteritems():
118 if curdir:
119 item_path = '/'.join((curdir, item))
120 else:
121 item_path = item
122 self._paths[item_path] = id
123 self._stat_modes[item_path] = stat
124 if dir == item:
129 if dir == item:
125 dir_id = id
130 dir_id = id
126 if dir_id:
131 if dir_id:
@@ -130,13 +135,16 b' class GitChangeset(BaseChangeset):'
130 raise ChangesetError('%s is not a directory' % curdir)
135 raise ChangesetError('%s is not a directory' % curdir)
131 else:
136 else:
132 raise ChangesetError('%s have not been found' % curdir)
137 raise ChangesetError('%s have not been found' % curdir)
133 for item, stat, id in tree.iteritems():
138
134 if curdir:
139 # cache all items from the given traversed tree
135 name = '/'.join((curdir, item))
140 for item, stat, id in tree.iteritems():
136 else:
141 if curdir:
137 name = item
142 name = '/'.join((curdir, item))
138 self._paths[name] = id
143 else:
139 self._stat_modes[name] = stat
144 name = item
145 self._paths[name] = id
146 self._stat_modes[name] = stat
147
140 if not path in self._paths:
148 if not path in self._paths:
141 raise NodeDoesNotExistError("There is no file nor directory "
149 raise NodeDoesNotExistError("There is no file nor directory "
142 "at the given path %r at revision %r"
150 "at the given path %r at revision %r"
@@ -85,14 +85,14 b' class BaseModel(object):'
85
85
86 if isinstance(instance, cls):
86 if isinstance(instance, cls):
87 return instance
87 return instance
88 elif isinstance(instance, int) or str(instance).isdigit():
88 elif isinstance(instance, (int, long)) or str(instance).isdigit():
89 return cls.get(instance)
89 return cls.get(instance)
90 else:
90 else:
91 if instance:
91 if instance:
92 if callback is None:
92 if callback is None:
93 raise Exception(
93 raise Exception(
94 'given object must be int or Instance of %s got %s, '
94 'given object must be int, long or Instance of %s '
95 'no callback provided' % (cls, type(instance))
95 'got %s, no callback provided' % (cls, type(instance))
96 )
96 )
97 else:
97 else:
98 return callback(instance)
98 return callback(instance)
@@ -29,7 +29,7 b' import traceback'
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib import extract_mentioned_users
32 from rhodecode.lib.utils2 import extract_mentioned_users
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
@@ -63,6 +63,7 b' class ChangesetCommentsModel(BaseModel):'
63 :param f_path:
63 :param f_path:
64 :param line_no:
64 :param line_no:
65 """
65 """
66
66 if text:
67 if text:
67 repo = Repository.get(repo_id)
68 repo = Repository.get(repo_id)
68 cs = repo.scm_instance.get_changeset(revision)
69 cs = repo.scm_instance.get_changeset(revision)
@@ -78,7 +79,6 b' class ChangesetCommentsModel(BaseModel):'
78
79
79 self.sa.add(comment)
80 self.sa.add(comment)
80 self.sa.flush()
81 self.sa.flush()
81
82 # make notification
82 # make notification
83 line = ''
83 line = ''
84 if line_no:
84 if line_no:
@@ -39,7 +39,8 b' from rhodecode.lib.vcs.utils.helpers imp'
39 from rhodecode.lib.vcs.exceptions import VCSError
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 safe_unicode
43 from rhodecode.lib.compat import json
44 from rhodecode.lib.compat import json
44 from rhodecode.lib.caching_query import FromCache
45 from rhodecode.lib.caching_query import FromCache
45
46
@@ -145,12 +146,18 b' class BaseModel(object):'
145 obj = cls.query().get(id_)
146 obj = cls.query().get(id_)
146 Session.delete(obj)
147 Session.delete(obj)
147
148
149 def __repr__(self):
150 if hasattr(self, '__unicode__'):
151 # python repr needs to return str
152 return safe_str(self.__unicode__())
153 return '<DB:%s>' % (self.__class__.__name__)
148
154
149 class RhodeCodeSetting(Base, BaseModel):
155 class RhodeCodeSetting(Base, BaseModel):
150 __tablename__ = 'rhodecode_settings'
156 __tablename__ = 'rhodecode_settings'
151 __table_args__ = (
157 __table_args__ = (
152 UniqueConstraint('app_settings_name'),
158 UniqueConstraint('app_settings_name'),
153 {'extend_existing': True}
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
160 'mysql_charset': 'utf8'}
154 )
161 )
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -181,8 +188,8 b' class RhodeCodeSetting(Base, BaseModel):'
181 """
188 """
182 self._app_settings_value = safe_unicode(val)
189 self._app_settings_value = safe_unicode(val)
183
190
184 def __repr__(self):
191 def __unicode__(self):
185 return "<%s('%s:%s')>" % (
192 return u"<%s('%s:%s')>" % (
186 self.__class__.__name__,
193 self.__class__.__name__,
187 self.app_settings_name, self.app_settings_value
194 self.app_settings_name, self.app_settings_value
188 )
195 )
@@ -224,7 +231,8 b' class RhodeCodeUi(Base, BaseModel):'
224 __tablename__ = 'rhodecode_ui'
231 __tablename__ = 'rhodecode_ui'
225 __table_args__ = (
232 __table_args__ = (
226 UniqueConstraint('ui_key'),
233 UniqueConstraint('ui_key'),
227 {'extend_existing': True}
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
235 'mysql_charset': 'utf8'}
228 )
236 )
229
237
230 HOOK_UPDATE = 'changegroup.update'
238 HOOK_UPDATE = 'changegroup.update'
@@ -274,7 +282,8 b' class User(Base, BaseModel):'
274 __tablename__ = 'users'
282 __tablename__ = 'users'
275 __table_args__ = (
283 __table_args__ = (
276 UniqueConstraint('username'), UniqueConstraint('email'),
284 UniqueConstraint('username'), UniqueConstraint('email'),
277 {'extend_existing': True}
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
286 'mysql_charset': 'utf8'}
278 )
287 )
279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -294,10 +303,15 b' class User(Base, BaseModel):'
294 repositories = relationship('Repository')
303 repositories = relationship('Repository')
295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
297
307
298 group_member = relationship('UsersGroupMember', cascade='all')
308 group_member = relationship('UsersGroupMember', cascade='all')
299
309
300 notifications = relationship('UserNotification',)
310 notifications = relationship('UserNotification', cascade='all')
311 # notifications assigned to this user
312 user_created_notifications = relationship('Notification', cascade='all')
313 # comments created by this user
314 user_comments = relationship('ChangesetComment', cascade='all')
301
315
302 @hybrid_property
316 @hybrid_property
303 def email(self):
317 def email(self):
@@ -328,8 +342,8 b' class User(Base, BaseModel):'
328 def is_admin(self):
342 def is_admin(self):
329 return self.admin
343 return self.admin
330
344
331 def __repr__(self):
345 def __unicode__(self):
332 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
333 self.user_id, self.username)
347 self.user_id, self.username)
334
348
335 @classmethod
349 @classmethod
@@ -376,6 +390,9 b' class User(Base, BaseModel):'
376
390
377 def __json__(self):
391 def __json__(self):
378 return dict(
392 return dict(
393 user_id=self.user_id,
394 first_name=self.name,
395 last_name=self.lastname,
379 email=self.email,
396 email=self.email,
380 full_name=self.full_name,
397 full_name=self.full_name,
381 full_name_or_username=self.full_name_or_username,
398 full_name_or_username=self.full_name_or_username,
@@ -386,7 +403,10 b' class User(Base, BaseModel):'
386
403
387 class UserLog(Base, BaseModel):
404 class UserLog(Base, BaseModel):
388 __tablename__ = 'user_logs'
405 __tablename__ = 'user_logs'
389 __table_args__ = {'extend_existing': True}
406 __table_args__ = (
407 {'extend_existing': True, 'mysql_engine':'InnoDB',
408 'mysql_charset': 'utf8'},
409 )
390 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
391 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
392 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
@@ -405,7 +425,10 b' class UserLog(Base, BaseModel):'
405
425
406 class UsersGroup(Base, BaseModel):
426 class UsersGroup(Base, BaseModel):
407 __tablename__ = 'users_groups'
427 __tablename__ = 'users_groups'
408 __table_args__ = {'extend_existing': True}
428 __table_args__ = (
429 {'extend_existing': True, 'mysql_engine':'InnoDB',
430 'mysql_charset': 'utf8'},
431 )
409
432
410 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -413,9 +436,10 b' class UsersGroup(Base, BaseModel):'
413
436
414 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
415 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
416
440
417 def __repr__(self):
441 def __unicode__(self):
418 return '<userGroup(%s)>' % (self.users_group_name)
442 return u'<userGroup(%s)>' % (self.users_group_name)
419
443
420 @classmethod
444 @classmethod
421 def get_by_group_name(cls, group_name, cache=False,
445 def get_by_group_name(cls, group_name, cache=False,
@@ -443,7 +467,10 b' class UsersGroup(Base, BaseModel):'
443
467
444 class UsersGroupMember(Base, BaseModel):
468 class UsersGroupMember(Base, BaseModel):
445 __tablename__ = 'users_groups_members'
469 __tablename__ = 'users_groups_members'
446 __table_args__ = {'extend_existing': True}
470 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine':'InnoDB',
472 'mysql_charset': 'utf8'},
473 )
447
474
448 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
449 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -461,7 +488,8 b' class Repository(Base, BaseModel):'
461 __tablename__ = 'repositories'
488 __tablename__ = 'repositories'
462 __table_args__ = (
489 __table_args__ = (
463 UniqueConstraint('repo_name'),
490 UniqueConstraint('repo_name'),
464 {'extend_existing': True},
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
492 'mysql_charset': 'utf8'},
465 )
493 )
466
494
467 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -489,9 +517,9 b' class Repository(Base, BaseModel):'
489
517
490 logs = relationship('UserLog')
518 logs = relationship('UserLog')
491
519
492 def __repr__(self):
520 def __unicode__(self):
493 return "<%s('%s:%s')>" % (self.__class__.__name__,
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
494 self.repo_id, self.repo_name)
522 self.repo_name)
495
523
496 @classmethod
524 @classmethod
497 def url_sep(cls):
525 def url_sep(cls):
@@ -710,7 +738,8 b' class RepoGroup(Base, BaseModel):'
710 __table_args__ = (
738 __table_args__ = (
711 UniqueConstraint('group_name', 'group_parent_id'),
739 UniqueConstraint('group_name', 'group_parent_id'),
712 CheckConstraint('group_id != group_parent_id'),
740 CheckConstraint('group_id != group_parent_id'),
713 {'extend_existing': True},
741 {'extend_existing': True, 'mysql_engine':'InnoDB',
742 'mysql_charset': 'utf8'},
714 )
743 )
715 __mapper_args__ = {'order_by': 'group_name'}
744 __mapper_args__ = {'order_by': 'group_name'}
716
745
@@ -728,8 +757,8 b' class RepoGroup(Base, BaseModel):'
728 self.group_name = group_name
757 self.group_name = group_name
729 self.parent_group = parent_group
758 self.parent_group = parent_group
730
759
731 def __repr__(self):
760 def __unicode__(self):
732 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
733 self.group_name)
762 self.group_name)
734
763
735 @classmethod
764 @classmethod
@@ -837,13 +866,16 b' class RepoGroup(Base, BaseModel):'
837
866
838 class Permission(Base, BaseModel):
867 class Permission(Base, BaseModel):
839 __tablename__ = 'permissions'
868 __tablename__ = 'permissions'
840 __table_args__ = {'extend_existing': True}
869 __table_args__ = (
870 {'extend_existing': True, 'mysql_engine':'InnoDB',
871 'mysql_charset': 'utf8'},
872 )
841 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
842 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
843 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
844
876
845 def __repr__(self):
877 def __unicode__(self):
846 return "<%s('%s:%s')>" % (
878 return u"<%s('%s:%s')>" % (
847 self.__class__.__name__, self.permission_id, self.permission_name
879 self.__class__.__name__, self.permission_id, self.permission_name
848 )
880 )
849
881
@@ -874,7 +906,8 b' class UserRepoToPerm(Base, BaseModel):'
874 __tablename__ = 'repo_to_perm'
906 __tablename__ = 'repo_to_perm'
875 __table_args__ = (
907 __table_args__ = (
876 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
877 {'extend_existing': True}
909 {'extend_existing': True, 'mysql_engine':'InnoDB',
910 'mysql_charset': 'utf8'}
878 )
911 )
879 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -894,15 +927,16 b' class UserRepoToPerm(Base, BaseModel):'
894 Session.add(n)
927 Session.add(n)
895 return n
928 return n
896
929
897 def __repr__(self):
930 def __unicode__(self):
898 return '<user:%s => %s >' % (self.user, self.repository)
931 return u'<user:%s => %s >' % (self.user, self.repository)
899
932
900
933
901 class UserToPerm(Base, BaseModel):
934 class UserToPerm(Base, BaseModel):
902 __tablename__ = 'user_to_perm'
935 __tablename__ = 'user_to_perm'
903 __table_args__ = (
936 __table_args__ = (
904 UniqueConstraint('user_id', 'permission_id'),
937 UniqueConstraint('user_id', 'permission_id'),
905 {'extend_existing': True}
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
939 'mysql_charset': 'utf8'}
906 )
940 )
907 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
908 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -916,7 +950,8 b' class UsersGroupRepoToPerm(Base, BaseMod'
916 __tablename__ = 'users_group_repo_to_perm'
950 __tablename__ = 'users_group_repo_to_perm'
917 __table_args__ = (
951 __table_args__ = (
918 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
919 {'extend_existing': True}
953 {'extend_existing': True, 'mysql_engine':'InnoDB',
954 'mysql_charset': 'utf8'}
920 )
955 )
921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -936,15 +971,16 b' class UsersGroupRepoToPerm(Base, BaseMod'
936 Session.add(n)
971 Session.add(n)
937 return n
972 return n
938
973
939 def __repr__(self):
974 def __unicode__(self):
940 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
941
976
942
977
943 class UsersGroupToPerm(Base, BaseModel):
978 class UsersGroupToPerm(Base, BaseModel):
944 __tablename__ = 'users_group_to_perm'
979 __tablename__ = 'users_group_to_perm'
945 __table_args__ = (
980 __table_args__ = (
946 UniqueConstraint('users_group_id', 'permission_id',),
981 UniqueConstraint('users_group_id', 'permission_id',),
947 {'extend_existing': True}
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
983 'mysql_charset': 'utf8'}
948 )
984 )
949 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -958,7 +994,8 b' class UserRepoGroupToPerm(Base, BaseMode'
958 __tablename__ = 'user_repo_group_to_perm'
994 __tablename__ = 'user_repo_group_to_perm'
959 __table_args__ = (
995 __table_args__ = (
960 UniqueConstraint('user_id', 'group_id', 'permission_id'),
996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
961 {'extend_existing': True}
997 {'extend_existing': True, 'mysql_engine':'InnoDB',
998 'mysql_charset': 'utf8'}
962 )
999 )
963
1000
964 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -975,7 +1012,8 b' class UsersGroupRepoGroupToPerm(Base, Ba'
975 __tablename__ = 'users_group_repo_group_to_perm'
1012 __tablename__ = 'users_group_repo_group_to_perm'
976 __table_args__ = (
1013 __table_args__ = (
977 UniqueConstraint('users_group_id', 'group_id'),
1014 UniqueConstraint('users_group_id', 'group_id'),
978 {'extend_existing': True}
1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1016 'mysql_charset': 'utf8'}
979 )
1017 )
980
1018
981 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)
1019 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)
@@ -990,7 +1028,11 b' class UsersGroupRepoGroupToPerm(Base, Ba'
990
1028
991 class Statistics(Base, BaseModel):
1029 class Statistics(Base, BaseModel):
992 __tablename__ = 'statistics'
1030 __tablename__ = 'statistics'
993 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
1031 __table_args__ = (
1032 UniqueConstraint('repository_id'),
1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1034 'mysql_charset': 'utf8'}
1035 )
994 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
996 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
@@ -1006,7 +1048,8 b' class UserFollowing(Base, BaseModel):'
1006 __table_args__ = (
1048 __table_args__ = (
1007 UniqueConstraint('user_id', 'follows_repository_id'),
1049 UniqueConstraint('user_id', 'follows_repository_id'),
1008 UniqueConstraint('user_id', 'follows_user_id'),
1050 UniqueConstraint('user_id', 'follows_user_id'),
1009 {'extend_existing': True}
1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1052 'mysql_charset': 'utf8'}
1010 )
1053 )
1011
1054
1012 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -1027,7 +1070,11 b' class UserFollowing(Base, BaseModel):'
1027
1070
1028 class CacheInvalidation(Base, BaseModel):
1071 class CacheInvalidation(Base, BaseModel):
1029 __tablename__ = 'cache_invalidation'
1072 __tablename__ = 'cache_invalidation'
1030 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1073 __table_args__ = (
1074 UniqueConstraint('cache_key'),
1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1076 'mysql_charset': 'utf8'},
1077 )
1031 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1032 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -1038,14 +1085,17 b' class CacheInvalidation(Base, BaseModel)'
1038 self.cache_args = cache_args
1085 self.cache_args = cache_args
1039 self.cache_active = False
1086 self.cache_active = False
1040
1087
1041 def __repr__(self):
1088 def __unicode__(self):
1042 return "<%s('%s:%s')>" % (self.__class__.__name__,
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1043 self.cache_id, self.cache_key)
1090 self.cache_id, self.cache_key)
1091 @classmethod
1092 def clear_cache(cls):
1093 cls.query().delete()
1044
1094
1045 @classmethod
1095 @classmethod
1046 def _get_key(cls, key):
1096 def _get_key(cls, key):
1047 """
1097 """
1048 Wrapper for generating a key
1098 Wrapper for generating a key, together with a prefix
1049
1099
1050 :param key:
1100 :param key:
1051 """
1101 """
@@ -1054,13 +1104,26 b' class CacheInvalidation(Base, BaseModel)'
1054 iid = rhodecode.CONFIG.get('instance_id')
1104 iid = rhodecode.CONFIG.get('instance_id')
1055 if iid:
1105 if iid:
1056 prefix = iid
1106 prefix = iid
1057 return "%s%s" % (prefix, key)
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1058
1108
1059 @classmethod
1109 @classmethod
1060 def get_by_key(cls, key):
1110 def get_by_key(cls, key):
1061 return cls.query().filter(cls.cache_key == key).scalar()
1111 return cls.query().filter(cls.cache_key == key).scalar()
1062
1112
1063 @classmethod
1113 @classmethod
1114 def _get_or_create_key(cls, key, prefix, org_key):
1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1116 if not inv_obj:
1117 try:
1118 inv_obj = CacheInvalidation(key, org_key)
1119 Session.add(inv_obj)
1120 Session.commit()
1121 except Exception:
1122 log.error(traceback.format_exc())
1123 Session.rollback()
1124 return inv_obj
1125
1126 @classmethod
1064 def invalidate(cls, key):
1127 def invalidate(cls, key):
1065 """
1128 """
1066 Returns Invalidation object if this given key should be invalidated
1129 Returns Invalidation object if this given key should be invalidated
@@ -1069,10 +1132,12 b' class CacheInvalidation(Base, BaseModel)'
1069
1132
1070 :param key:
1133 :param key:
1071 """
1134 """
1072 return cls.query()\
1135
1073 .filter(CacheInvalidation.cache_key == key)\
1136 key, _prefix, _org_key = cls._get_key(key)
1074 .filter(CacheInvalidation.cache_active == False)\
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1075 .scalar()
1138
1139 if inv and inv.cache_active is False:
1140 return inv
1076
1141
1077 @classmethod
1142 @classmethod
1078 def set_invalidate(cls, key):
1143 def set_invalidate(cls, key):
@@ -1082,17 +1147,16 b' class CacheInvalidation(Base, BaseModel)'
1082 :param key:
1147 :param key:
1083 """
1148 """
1084
1149
1085 log.debug('marking %s for invalidation' % key)
1150 key, _prefix, _org_key = cls._get_key(key)
1086 inv_obj = Session.query(cls)\
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1087 .filter(cls.cache_key == key).scalar()
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1088 if inv_obj:
1153 _org_key))
1089 inv_obj.cache_active = False
1154 try:
1090 else:
1155 for inv_obj in inv_objs:
1091 log.debug('cache key not found in invalidation db -> creating one')
1156 if inv_obj:
1092 inv_obj = CacheInvalidation(key)
1157 inv_obj.cache_active = False
1093
1158
1094 try:
1159 Session.add(inv_obj)
1095 Session.add(inv_obj)
1096 Session.commit()
1160 Session.commit()
1097 except Exception:
1161 except Exception:
1098 log.error(traceback.format_exc())
1162 log.error(traceback.format_exc())
@@ -1113,7 +1177,10 b' class CacheInvalidation(Base, BaseModel)'
1113
1177
1114 class ChangesetComment(Base, BaseModel):
1178 class ChangesetComment(Base, BaseModel):
1115 __tablename__ = 'changeset_comments'
1179 __tablename__ = 'changeset_comments'
1116 __table_args__ = ({'extend_existing': True},)
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1182 'mysql_charset': 'utf8'},
1183 )
1117 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1118 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1119 revision = Column('revision', String(40), nullable=False)
1186 revision = Column('revision', String(40), nullable=False)
@@ -1142,7 +1209,10 b' class ChangesetComment(Base, BaseModel):'
1142
1209
1143 class Notification(Base, BaseModel):
1210 class Notification(Base, BaseModel):
1144 __tablename__ = 'notifications'
1211 __tablename__ = 'notifications'
1145 __table_args__ = ({'extend_existing': True},)
1212 __table_args__ = (
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1214 'mysql_charset': 'utf8'},
1215 )
1146
1216
1147 TYPE_CHANGESET_COMMENT = u'cs_comment'
1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1148 TYPE_MESSAGE = u'message'
1218 TYPE_MESSAGE = u'message'
@@ -1194,7 +1264,8 b' class UserNotification(Base, BaseModel):'
1194 __tablename__ = 'user_to_notification'
1264 __tablename__ = 'user_to_notification'
1195 __table_args__ = (
1265 __table_args__ = (
1196 UniqueConstraint('user_id', 'notification_id'),
1266 UniqueConstraint('user_id', 'notification_id'),
1197 {'extend_existing': True}
1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 'mysql_charset': 'utf8'}
1198 )
1269 )
1199 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1200 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1271 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
@@ -1212,7 +1283,10 b' class UserNotification(Base, BaseModel):'
1212
1283
1213 class DbMigrateVersion(Base, BaseModel):
1284 class DbMigrateVersion(Base, BaseModel):
1214 __tablename__ = 'db_migrate_version'
1285 __tablename__ = 'db_migrate_version'
1215 __table_args__ = {'extend_existing': True}
1286 __table_args__ = (
1287 {'extend_existing': True, 'mysql_engine':'InnoDB',
1288 'mysql_charset': 'utf8'},
1289 )
1216 repository_id = Column('repository_id', String(250), primary_key=True)
1290 repository_id = Column('repository_id', String(250), primary_key=True)
1217 repository_path = Column('repository_path', Text)
1291 repository_path = Column('repository_path', Text)
1218 version = Column('version', Integer)
1292 version = Column('version', Integer)
@@ -32,6 +32,7 b' import datetime'
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.config.conf import DATETIME_FORMAT
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.model.db import Notification, User, UserNotification
@@ -47,11 +48,11 b' class NotificationModel(BaseModel):'
47 def __get_notification(self, notification):
48 def __get_notification(self, notification):
48 if isinstance(notification, Notification):
49 if isinstance(notification, Notification):
49 return notification
50 return notification
50 elif isinstance(notification, int):
51 elif isinstance(notification, (int, long)):
51 return Notification.get(notification)
52 return Notification.get(notification)
52 else:
53 else:
53 if notification:
54 if notification:
54 raise Exception('notification must be int or Instance'
55 raise Exception('notification must be int, long or Instance'
55 ' of Notification got %s' % type(notification))
56 ' of Notification got %s' % type(notification))
56
57
57 def create(self, created_by, subject, body, recipients=None,
58 def create(self, created_by, subject, body, recipients=None,
@@ -111,6 +112,7 b' class NotificationModel(BaseModel):'
111 kwargs.update(email_kwargs)
112 kwargs.update(email_kwargs)
112 email_body_html = EmailNotificationModel()\
113 email_body_html = EmailNotificationModel()\
113 .get_email_tmpl(type_, **kwargs)
114 .get_email_tmpl(type_, **kwargs)
115
114 run_task(tasks.send_email, rec.email, email_subject, email_body,
116 run_task(tasks.send_email, rec.email, email_subject, email_body,
115 email_body_html)
117 email_body_html)
116
118
@@ -176,14 +178,13 b' class NotificationModel(BaseModel):'
176 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
178 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
177 }
179 }
178
180
179 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
180
181 tmpl = "%(user)s %(action)s %(when)s"
181 tmpl = "%(user)s %(action)s %(when)s"
182 if show_age:
182 if show_age:
183 when = h.age(notification.created_on)
183 when = h.age(notification.created_on)
184 else:
184 else:
185 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
185 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
186 when = DTF(notification.created_on)
186 when = DTF(notification.created_on)
187
187 data = dict(
188 data = dict(
188 user=notification.created_by_user.username,
189 user=notification.created_by_user.username,
189 action=_map[notification.type_], when=when,
190 action=_map[notification.type_], when=when,
@@ -29,15 +29,15 b' import traceback'
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from rhodecode.lib.vcs.backends import get_backend
31 from rhodecode.lib.vcs.backends import get_backend
32
32 from rhodecode.lib.compat import json
33 from rhodecode.lib import LazyProperty
33 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
34 from rhodecode.lib import safe_str, safe_unicode
35 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.hooks import log_create_repository
35 from rhodecode.lib.hooks import log_create_repository
37
36
38 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
38 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 from rhodecode.lib import helpers as h
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
@@ -95,25 +95,28 b' class RepoModel(BaseModel):'
95 return repo.scalar()
95 return repo.scalar()
96
96
97 def get_users_js(self):
97 def get_users_js(self):
98
99 users = self.sa.query(User).filter(User.active == True).all()
98 users = self.sa.query(User).filter(User.active == True).all()
100 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
99 return json.dumps([
101 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
100 {
102 u.lastname, u.username)
101 'id': u.user_id,
103 for u in users])
102 'fname': u.name,
104 return users_array
103 'lname': u.lastname,
104 'nname': u.username,
105 'gravatar_lnk': h.gravatar_url(u.email, 14)
106 } for u in users]
107 )
105
108
106 def get_users_groups_js(self):
109 def get_users_groups_js(self):
107 users_groups = self.sa.query(UsersGroup)\
110 users_groups = self.sa.query(UsersGroup)\
108 .filter(UsersGroup.users_group_active == True).all()
111 .filter(UsersGroup.users_group_active == True).all()
109
112
110 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
113 return json.dumps([
111
114 {
112 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
115 'id': gr.users_group_id,
113 (gr.users_group_id, gr.users_group_name,
116 'grname': gr.users_group_name,
114 len(gr.members))
117 'grmembers': len(gr.members),
115 for gr in users_groups])
118 } for gr in users_groups]
116 return users_groups_array
119 )
117
120
118 def _get_defaults(self, repo_name):
121 def _get_defaults(self, repo_name):
119 """
122 """
@@ -346,6 +349,7 b' class RepoModel(BaseModel):'
346 :param repo: Instance of Repository, repository_id, or repository name
349 :param repo: Instance of Repository, repository_id, or repository name
347 :param user: Instance of User, user_id or username
350 :param user: Instance of User, user_id or username
348 """
351 """
352
349 user = self.__get_user(user)
353 user = self.__get_user(user)
350 repo = self.__get_repo(repo)
354 repo = self.__get_repo(repo)
351
355
@@ -28,7 +28,7 b' import logging'
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from rhodecode.lib import LazyProperty
31 from rhodecode.lib.utils2 import LazyProperty
32
32
33 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
@@ -35,7 +35,7 b' from rhodecode.lib.vcs.nodes import File'
35
35
36 from rhodecode import BACKENDS
36 from rhodecode import BACKENDS
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import safe_str
38 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
@@ -235,13 +235,13 b' class ScmModel(BaseModel):'
235 return group_iter
235 return group_iter
236
236
237 def mark_for_invalidation(self, repo_name):
237 def mark_for_invalidation(self, repo_name):
238 """Puts cache invalidation task into db for
238 """
239 Puts cache invalidation task into db for
239 further global cache invalidation
240 further global cache invalidation
240
241
241 :param repo_name: this repo that should invalidation take place
242 :param repo_name: this repo that should invalidation take place
242 """
243 """
243 CacheInvalidation.set_invalidate(repo_name)
244 CacheInvalidation.set_invalidate(repo_name)
244 CacheInvalidation.set_invalidate(repo_name + "_README")
245
245
246 def toggle_following_repo(self, follow_repo_id, user_id):
246 def toggle_following_repo(self, follow_repo_id, user_id):
247
247
@@ -29,18 +29,19 b' import traceback'
29 from pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib import safe_unicode
32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
39 UsersGroupRepoGroupToPerm
39 from rhodecode.lib.exceptions import DefaultUserException, \
40 from rhodecode.lib.exceptions import DefaultUserException, \
40 UserOwnsReposException
41 UserOwnsReposException
41
42
42 from sqlalchemy.exc import DatabaseError
43 from sqlalchemy.exc import DatabaseError
43 from rhodecode.lib import generate_api_key
44
44 from sqlalchemy.orm import joinedload
45 from sqlalchemy.orm import joinedload
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
@@ -298,14 +299,16 b' class UserModel(BaseModel):'
298 try:
299 try:
299 if user.username == 'default':
300 if user.username == 'default':
300 raise DefaultUserException(
301 raise DefaultUserException(
301 _("You can't remove this user since it's"
302 _(u"You can't remove this user since it's"
302 " crucial for entire application"))
303 " crucial for entire application")
304 )
303 if user.repositories:
305 if user.repositories:
304 raise UserOwnsReposException(_('This user still owns %s '
306 repos = [x.repo_name for x in user.repositories]
305 'repositories and cannot be '
307 raise UserOwnsReposException(
306 'removed. Switch owners or '
308 _(u'user "%s" still owns %s repositories and cannot be '
307 'remove those repositories') \
309 'removed. Switch owners or remove those repositories. %s')
308 % user.repositories)
310 % (user.username, len(repos), ', '.join(repos))
311 )
309 self.sa.delete(user)
312 self.sa.delete(user)
310 except:
313 except:
311 log.error(traceback.format_exc())
314 log.error(traceback.format_exc())
@@ -409,7 +412,7 b' class UserModel(BaseModel):'
409 for perm in default_global_perms:
412 for perm in default_global_perms:
410 user.permissions[GLOBAL].add(perm.permission.permission_name)
413 user.permissions[GLOBAL].add(perm.permission.permission_name)
411
414
412 # default for repositories
415 # defaults for repositories, taken from default user
413 for perm in default_repo_perms:
416 for perm in default_repo_perms:
414 r_k = perm.UserRepoToPerm.repository.repo_name
417 r_k = perm.UserRepoToPerm.repository.repo_name
415 if perm.Repository.private and not (perm.Repository.user_id == uid):
418 if perm.Repository.private and not (perm.Repository.user_id == uid):
@@ -423,17 +426,18 b' class UserModel(BaseModel):'
423
426
424 user.permissions[RK][r_k] = p
427 user.permissions[RK][r_k] = p
425
428
426 # default for repositories groups
429 # defaults for repositories groups taken from default user permission
430 # on given group
427 for perm in default_repo_groups_perms:
431 for perm in default_repo_groups_perms:
428 rg_k = perm.UserRepoGroupToPerm.group.group_name
432 rg_k = perm.UserRepoGroupToPerm.group.group_name
429 p = perm.Permission.permission_name
433 p = perm.Permission.permission_name
430 user.permissions[GK][rg_k] = p
434 user.permissions[GK][rg_k] = p
431
435
432 #==================================================================
436 #==================================================================
433 # overwrite default with user permissions if any
437 # overwrite defaults with user permissions if any found
434 #==================================================================
438 #==================================================================
435
439
436 # user global
440 # user global permissions
437 user_perms = self.sa.query(UserToPerm)\
441 user_perms = self.sa.query(UserToPerm)\
438 .options(joinedload(UserToPerm.permission))\
442 .options(joinedload(UserToPerm.permission))\
439 .filter(UserToPerm.user_id == uid).all()
443 .filter(UserToPerm.user_id == uid).all()
@@ -441,7 +445,7 b' class UserModel(BaseModel):'
441 for perm in user_perms:
445 for perm in user_perms:
442 user.permissions[GLOBAL].add(perm.permission.permission_name)
446 user.permissions[GLOBAL].add(perm.permission.permission_name)
443
447
444 # user repositories
448 # user explicit permissions for repositories
445 user_repo_perms = \
449 user_repo_perms = \
446 self.sa.query(UserRepoToPerm, Permission, Repository)\
450 self.sa.query(UserRepoToPerm, Permission, Repository)\
447 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
451 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
@@ -459,8 +463,8 b' class UserModel(BaseModel):'
459 user.permissions[RK][r_k] = p
463 user.permissions[RK][r_k] = p
460
464
461 #==================================================================
465 #==================================================================
462 # check if user is part of groups for this repository and fill in
466 # check if user is part of user groups for this repository and
463 # (or replace with higher) permissions
467 # fill in (or replace with higher) permissions
464 #==================================================================
468 #==================================================================
465
469
466 # users group global
470 # users group global
@@ -473,7 +477,7 b' class UserModel(BaseModel):'
473 for perm in user_perms_from_users_groups:
477 for perm in user_perms_from_users_groups:
474 user.permissions[GLOBAL].add(perm.permission.permission_name)
478 user.permissions[GLOBAL].add(perm.permission.permission_name)
475
479
476 # users group repositories
480 # users group for repositories permissions
477 user_repo_perms_from_users_groups = \
481 user_repo_perms_from_users_groups = \
478 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
482 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
479 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
483 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
@@ -495,12 +499,12 b' class UserModel(BaseModel):'
495 # get access for this user for repos group and override defaults
499 # get access for this user for repos group and override defaults
496 #==================================================================
500 #==================================================================
497
501
498 # user repositories groups
502 # user explicit permissions for repository
499 user_repo_groups_perms = \
503 user_repo_groups_perms = \
500 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
504 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
501 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
502 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
503 .filter(UserRepoToPerm.user_id == uid)\
507 .filter(UserRepoGroupToPerm.user_id == uid)\
504 .all()
508 .all()
505
509
506 for perm in user_repo_groups_perms:
510 for perm in user_repo_groups_perms:
@@ -510,6 +514,30 b' class UserModel(BaseModel):'
510 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
514 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
511 user.permissions[GK][rg_k] = p
515 user.permissions[GK][rg_k] = p
512
516
517 #==================================================================
518 # check if user is part of user groups for this repo group and
519 # fill in (or replace with higher) permissions
520 #==================================================================
521
522 # users group for repositories permissions
523 user_repo_group_perms_from_users_groups = \
524 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
525 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
526 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
527 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
528 .filter(UsersGroupMember.user_id == uid)\
529 .all()
530
531 for perm in user_repo_group_perms_from_users_groups:
532 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
533 print perm, g_k
534 p = perm.Permission.permission_name
535 cur_perm = user.permissions[GK][g_k]
536 # overwrite permission only if it's greater than permission
537 # given from other sources
538 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
539 user.permissions[GK][g_k] = p
540
513 return user
541 return user
514
542
515 def has_perm(self, user, perm):
543 def has_perm(self, user, perm):
@@ -185,6 +185,41 b' div.options a {'
185 border-bottom-right-radius: 8px;
185 border-bottom-right-radius: 8px;
186 }
186 }
187
187
188 .top-left-rounded-corner-mid {
189 -webkit-border-top-left-radius: 4px;
190 -khtml-border-radius-topleft: 4px;
191 -moz-border-radius-topleft: 4px;
192 border-top-left-radius: 4px;
193 }
194
195 .top-right-rounded-corner-mid {
196 -webkit-border-top-right-radius: 4px;
197 -khtml-border-radius-topright: 4px;
198 -moz-border-radius-topright: 4px;
199 border-top-right-radius: 4px;
200 }
201
202 .bottom-left-rounded-corner-mid {
203 -webkit-border-bottom-left-radius: 4px;
204 -khtml-border-radius-bottomleft: 4px;
205 -moz-border-radius-bottomleft: 4px;
206 border-bottom-left-radius: 4px;
207 }
208
209 .bottom-right-rounded-corner-mid {
210 -webkit-border-bottom-right-radius: 4px;
211 -khtml-border-radius-bottomright: 4px;
212 -moz-border-radius-bottomright: 4px;
213 border-bottom-right-radius: 4px;
214 }
215
216 .help-block {
217 color: #999999;
218 display: block;
219 margin-bottom: 0;
220 margin-top: 5px;
221 }
222
188 #header {
223 #header {
189 margin: 0;
224 margin: 0;
190 padding: 0 10px;
225 padding: 0 10px;
@@ -197,18 +232,16 b' div.options a {'
197 -moz-border-radius: 0px 0px 8px 8px;
232 -moz-border-radius: 0px 0px 8px 8px;
198 border-radius: 0px 0px 8px 8px;
233 border-radius: 0px 0px 8px 8px;
199 height: 37px;
234 height: 37px;
200 background-color: #eedc94;
235 background-color: #003B76;
201 background-repeat: repeat-x;
236 background-repeat: repeat-x;
202 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
237 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
203 to(#eedc94) );
204 background-image: -moz-linear-gradient(top, #003b76, #00376e);
238 background-image: -moz-linear-gradient(top, #003b76, #00376e);
205 background-image: -ms-linear-gradient(top, #003b76, #00376e);
239 background-image: -ms-linear-gradient(top, #003b76, #00376e);
206 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
240 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
207 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
241 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
208 background-image: -o-linear-gradient(top, #003b76, #00376e);
242 background-image: -o-linear-gradient(top, #003b76, #00376e);
209 background-image: linear-gradient(top, #003b76, #00376e);
243 background-image: linear-gradient(top, #003b76, #00376e);
210 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
244 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
211 endColorstr='#00376e', GradientType=0 );
212 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
245 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
213 }
246 }
214
247
@@ -260,9 +293,9 b' div.options a {'
260 min-height: 44px;
293 min-height: 44px;
261 clear: both;
294 clear: both;
262 position: relative;
295 position: relative;
263 background-color: #eedc94;
296 background-color: #003B76;
264 background-repeat: repeat-x;
297 background-repeat: repeat-x;
265 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),to(#eedc94) );
298 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
266 background-image: -moz-linear-gradient(top, #003b76, #00376e);
299 background-image: -moz-linear-gradient(top, #003b76, #00376e);
267 background-image: -ms-linear-gradient(top, #003b76, #00376e);
300 background-image: -ms-linear-gradient(top, #003b76, #00376e);
268 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
301 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
@@ -289,6 +322,14 b' div.options a {'
289 -moz-border-radius: 0px 0px 0px 0px;
322 -moz-border-radius: 0px 0px 0px 0px;
290 border-radius: 0px 0px 0px 0px;
323 border-radius: 0px 0px 0px 0px;
291 }
324 }
325
326 .ie7 #header #header-inner.hover,
327 .ie8 #header #header-inner.hover,
328 .ie9 #header #header-inner.hover
329 {
330 z-index: auto !important;
331 }
332
292 #header #header-inner #home a {
333 #header #header-inner #home a {
293 height: 40px;
334 height: 40px;
294 width: 46px;
335 width: 46px;
@@ -997,9 +1038,9 b' tbody .yui-dt-editable { cursor: pointer'
997 #content div.box div.title {
1038 #content div.box div.title {
998 clear: both;
1039 clear: both;
999 overflow: hidden;
1040 overflow: hidden;
1000 background-color: #eedc94;
1041 background-color: #003B76;
1001 background-repeat: repeat-x;
1042 background-repeat: repeat-x;
1002 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
1043 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1003 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1044 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1004 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1045 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1005 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1046 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
@@ -1758,33 +1799,21 b' div.form div.fields div.field div.button'
1758 }
1799 }
1759
1800
1760 #footer div#footer-inner {
1801 #footer div#footer-inner {
1761 background-color: #eedc94; background-repeat : repeat-x;
1802 background-color: #003B76;
1762 background-image : -khtml-gradient( linear, left top, left bottom,
1803 background-repeat : repeat-x;
1763 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1804 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1764 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1805 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1765 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1806 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1766 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1807 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1767 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1808 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1768 background-image : -o-linear-gradient( top, #003b76, #00376e));
1809 background-image : -o-linear-gradient( top, #003b76, #00376e));
1769 background-image : linear-gradient( top, #003b76, #00376e); filter :
1810 background-image : linear-gradient( top, #003b76, #00376e);
1770 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1811 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1771 '#003b76', endColorstr = '#00376e', GradientType = 0);
1772 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1812 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1773 -webkit-border-radius: 4px 4px 4px 4px;
1813 -webkit-border-radius: 4px 4px 4px 4px;
1774 -khtml-border-radius: 4px 4px 4px 4px;
1814 -khtml-border-radius: 4px 4px 4px 4px;
1775 -moz-border-radius: 4px 4px 4px 4px;
1815 -moz-border-radius: 4px 4px 4px 4px;
1776 border-radius: 4px 4px 4px 4px;
1816 border-radius: 4px 4px 4px 4px;
1777 background-repeat: repeat-x;
1778 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1779 to(#eedc94) );
1780 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1781 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1782 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1783 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1784 background-image: -o-linear-gradient(top, #003b76, #00376e);
1785 background-image: linear-gradient(top, #003b76, #00376e);
1786 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1787 endColorstr='#00376e', GradientType=0 );
1788 }
1817 }
1789
1818
1790 #footer div#footer-inner p {
1819 #footer div#footer-inner p {
@@ -1808,30 +1837,18 b' div.form div.fields div.field div.button'
1808 clear: both;
1837 clear: both;
1809 overflow: hidden;
1838 overflow: hidden;
1810 position: relative;
1839 position: relative;
1811 background-color: #eedc94; background-repeat : repeat-x;
1840 background-color: #003B76;
1812 background-image : -khtml-gradient( linear, left top, left bottom,
1841 background-repeat : repeat-x;
1813 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1842 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1814 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1843 background-image : -moz-linear-gradient( top, #003b76, #00376e);
1815 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1844 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1816 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1845 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1817 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1846 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1818 background-image : -o-linear-gradient( top, #003b76, #00376e));
1847 background-image : -o-linear-gradient( top, #003b76, #00376e));
1819 background-image : linear-gradient( top, #003b76, #00376e); filter :
1848 background-image : linear-gradient( top, #003b76, #00376e);
1820 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1849 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1821 '#003b76', endColorstr = '#00376e', GradientType = 0);
1822 margin: 0 auto;
1850 margin: 0 auto;
1823 padding: 0;
1851 padding: 0;
1824 background-repeat: repeat-x;
1825 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1826 to(#eedc94) );
1827 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1828 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1829 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1830 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1831 background-image: -o-linear-gradient(top, #003b76, #00376e);
1832 background-image: linear-gradient(top, #003b76, #00376e);
1833 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1834 endColorstr='#00376e', GradientType=0 );
1835 }
1852 }
1836
1853
1837 #login div.inner {
1854 #login div.inner {
@@ -1908,16 +1925,14 b' div.form div.fields div.field div.button'
1908 width: 278px;
1925 width: 278px;
1909
1926
1910 background-repeat: repeat-x;
1927 background-repeat: repeat-x;
1911 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1928 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1912 to(#eedc94) );
1913 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1929 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1914 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1930 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1915 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1931 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1916 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1932 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1917 background-image: -o-linear-gradient(top, #003b76, #00376e);
1933 background-image: -o-linear-gradient(top, #003b76, #00376e);
1918 background-image: linear-gradient(top, #003b76, #00376e);
1934 background-image: linear-gradient(top, #003b76, #00376e);
1919 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1935 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1920 endColorstr='#00376e', GradientType=0 );
1921
1936
1922 z-index: 999;
1937 z-index: 999;
1923 -webkit-border-radius: 0px 0px 4px 4px;
1938 -webkit-border-radius: 0px 0px 4px 4px;
@@ -2060,10 +2075,9 b' div.form div.fields div.field div.button'
2060 clear: both;
2075 clear: both;
2061 overflow: hidden;
2076 overflow: hidden;
2062 position: relative;
2077 position: relative;
2063 background-color: #eedc94;
2078 background-color: #003B76;
2064 background-repeat: repeat-x;
2079 background-repeat: repeat-x;
2065 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
2080 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2066 to(#eedc94) );
2067 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2081 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2068 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2082 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2069 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2083 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
@@ -2794,12 +2808,12 b' table.code-browser .browser-dir {'
2794 }
2808 }
2795
2809
2796 .ac .yui-ac {
2810 .ac .yui-ac {
2797 position: relative;
2811 position: inherit;
2798 font-size: 100%;
2812 font-size: 100%;
2799 }
2813 }
2800
2814
2801 .ac .perm_ac {
2815 .ac .perm_ac {
2802 width: 15em;
2816 width: 20em;
2803 }
2817 }
2804
2818
2805 .ac .yui-ac-input {
2819 .ac .yui-ac-input {
@@ -2809,16 +2823,15 b' table.code-browser .browser-dir {'
2809 .ac .yui-ac-container {
2823 .ac .yui-ac-container {
2810 position: absolute;
2824 position: absolute;
2811 top: 1.6em;
2825 top: 1.6em;
2812 width: 100%;
2826 width: auto;
2813 }
2827 }
2814
2828
2815 .ac .yui-ac-content {
2829 .ac .yui-ac-content {
2816 position: absolute;
2830 position: absolute;
2817 width: 100%;
2818 border: 1px solid gray;
2831 border: 1px solid gray;
2819 background: #fff;
2832 background: #fff;
2820 overflow: hidden;
2821 z-index: 9050;
2833 z-index: 9050;
2834
2822 }
2835 }
2823
2836
2824 .ac .yui-ac-shadow {
2837 .ac .yui-ac-shadow {
@@ -2827,7 +2840,7 b' table.code-browser .browser-dir {'
2827 background: #000;
2840 background: #000;
2828 -moz-opacity: 0.1px;
2841 -moz-opacity: 0.1px;
2829 opacity: .10;
2842 opacity: .10;
2830 filter: alpha(opacity = 10);
2843 filter: alpha(opacity = 10);
2831 z-index: 9049;
2844 z-index: 9049;
2832 margin: .3em;
2845 margin: .3em;
2833 }
2846 }
@@ -2836,6 +2849,7 b' table.code-browser .browser-dir {'
2836 width: 100%;
2849 width: 100%;
2837 margin: 0;
2850 margin: 0;
2838 padding: 0;
2851 padding: 0;
2852 z-index: 9050;
2839 }
2853 }
2840
2854
2841 .ac .yui-ac-content li {
2855 .ac .yui-ac-content li {
@@ -2843,15 +2857,28 b' table.code-browser .browser-dir {'
2843 white-space: nowrap;
2857 white-space: nowrap;
2844 margin: 0;
2858 margin: 0;
2845 padding: 2px 5px;
2859 padding: 2px 5px;
2860 height: 18px;
2861 z-index: 9050;
2862 display: block;
2863 width: auto !important;
2864 }
2865
2866 .ac .yui-ac-content li .ac-container-wrap{
2867 width: auto;
2846 }
2868 }
2847
2869
2848 .ac .yui-ac-content li.yui-ac-prehighlight {
2870 .ac .yui-ac-content li.yui-ac-prehighlight {
2849 background: #B3D4FF;
2871 background: #B3D4FF;
2872 z-index: 9050;
2850 }
2873 }
2851
2874
2852 .ac .yui-ac-content li.yui-ac-highlight {
2875 .ac .yui-ac-content li.yui-ac-highlight {
2853 background: #556CB5;
2876 background: #556CB5;
2854 color: #FFF;
2877 color: #FFF;
2878 z-index: 9050;
2879 }
2880 .ac .yui-ac-bd{
2881 z-index: 9050;
2855 }
2882 }
2856
2883
2857 .follow {
2884 .follow {
@@ -3006,17 +3033,14 b' table.code-browser .browser-dir {'
3006 .error_msg {
3033 .error_msg {
3007 background-color: #c43c35;
3034 background-color: #c43c35;
3008 background-repeat: repeat-x;
3035 background-repeat: repeat-x;
3009 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b),
3036 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3010 to(#c43c35) );
3011 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3037 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3012 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3038 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3013 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b),
3039 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3014 color-stop(100%, #c43c35) );
3015 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3040 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3016 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3041 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3017 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3042 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3018 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',
3043 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3019 endColorstr='#c43c35', GradientType=0 );
3020 border-color: #c43c35 #c43c35 #882a25;
3044 border-color: #c43c35 #c43c35 #882a25;
3021 }
3045 }
3022
3046
@@ -3024,51 +3048,42 b' table.code-browser .browser-dir {'
3024 color: #404040 !important;
3048 color: #404040 !important;
3025 background-color: #eedc94;
3049 background-color: #eedc94;
3026 background-repeat: repeat-x;
3050 background-repeat: repeat-x;
3027 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
3051 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3028 to(#eedc94) );
3029 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3052 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3030 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3053 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3031 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1),
3054 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3032 color-stop(100%, #eedc94) );
3033 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3055 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3034 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3056 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3035 background-image: linear-gradient(top, #fceec1, #eedc94);
3057 background-image: linear-gradient(top, #fceec1, #eedc94);
3036 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1',
3058 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3037 endColorstr='#eedc94', GradientType=0 );
3038 border-color: #eedc94 #eedc94 #e4c652;
3059 border-color: #eedc94 #eedc94 #e4c652;
3039 }
3060 }
3040
3061
3041 .success_msg {
3062 .success_msg {
3042 background-color: #57a957;
3063 background-color: #57a957;
3043 background-repeat: repeat-x !important;
3064 background-repeat: repeat-x !important;
3044 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462),
3065 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3045 to(#57a957) );
3046 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3066 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3047 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3067 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3048 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462),
3068 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3049 color-stop(100%, #57a957) );
3050 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3069 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3051 background-image: -o-linear-gradient(top, #62c462, #57a957);
3070 background-image: -o-linear-gradient(top, #62c462, #57a957);
3052 background-image: linear-gradient(top, #62c462, #57a957);
3071 background-image: linear-gradient(top, #62c462, #57a957);
3053 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',
3072 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3054 endColorstr='#57a957', GradientType=0 );
3055 border-color: #57a957 #57a957 #3d773d;
3073 border-color: #57a957 #57a957 #3d773d;
3056 }
3074 }
3057
3075
3058 .notice_msg {
3076 .notice_msg {
3059 background-color: #339bb9;
3077 background-color: #339bb9;
3060 background-repeat: repeat-x;
3078 background-repeat: repeat-x;
3061 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de),
3079 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3062 to(#339bb9) );
3063 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3080 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3064 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3081 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3065 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de),
3082 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3066 color-stop(100%, #339bb9) );
3067 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3083 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3068 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3084 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3069 background-image: linear-gradient(top, #5bc0de, #339bb9);
3085 background-image: linear-gradient(top, #5bc0de, #339bb9);
3070 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',
3086 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3071 endColorstr='#339bb9', GradientType=0 );
3072 border-color: #339bb9 #339bb9 #22697d;
3087 border-color: #339bb9 #339bb9 #22697d;
3073 }
3088 }
3074
3089
@@ -3096,8 +3111,7 b' table.code-browser .browser-dir {'
3096 }
3111 }
3097
3112
3098 #msg_close {
3113 #msg_close {
3099 background: transparent url("../icons/cross_grey_small.png") no-repeat
3114 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3100 scroll 0 0;
3101 cursor: pointer;
3115 cursor: pointer;
3102 height: 16px;
3116 height: 16px;
3103 position: absolute;
3117 position: absolute;
@@ -3105,7 +3119,12 b' table.code-browser .browser-dir {'
3105 top: 5px;
3119 top: 5px;
3106 width: 16px;
3120 width: 16px;
3107 }
3121 }
3108
3122 div#legend_data{
3123 padding-left:10px;
3124 }
3125 div#legend_container table{
3126 border: none !important;
3127 }
3109 div#legend_container table,div#legend_choices table {
3128 div#legend_container table,div#legend_choices table {
3110 width: auto !important;
3129 width: auto !important;
3111 }
3130 }
@@ -4115,6 +4134,56 b' form.comment-inline-form {'
4115 padding:5px 0px 5px 38px;
4134 padding:5px 0px 5px 38px;
4116 }
4135 }
4117
4136
4137 /****
4138 PERMS
4139 *****/
4140 #perms .perms_section_head {
4141 padding:10px 10px 10px 0px;
4142 font-size:16px;
4143 font-weight: bold;
4144 }
4145
4146 #perms .perm_tag{
4147 padding: 1px 3px 1px 3px;
4148 font-size: 10px;
4149 font-weight: bold;
4150 text-transform: uppercase;
4151 white-space: nowrap;
4152 -webkit-border-radius: 3px;
4153 -moz-border-radius: 3px;
4154 border-radius: 3px;
4155 }
4156
4157 #perms .perm_tag.admin{
4158 background-color: #B94A48;
4159 color: #ffffff;
4160 }
4161
4162 #perms .perm_tag.write{
4163 background-color: #B94A48;
4164 color: #ffffff;
4165 }
4166
4167 #perms .perm_tag.read{
4168 background-color: #468847;
4169 color: #ffffff;
4170 }
4171
4172 #perms .perm_tag.none{
4173 background-color: #bfbfbf;
4174 color: #ffffff;
4175 }
4176
4177 .perm-gravatar{
4178 vertical-align:middle;
4179 padding:2px;
4180 }
4181 .perm-gravatar-ac{
4182 vertical-align:middle;
4183 padding:2px;
4184 width: 14px;
4185 height: 14px;
4186 }
4118
4187
4119 /*****************************************************************************
4188 /*****************************************************************************
4120 DIFFS CSS
4189 DIFFS CSS
@@ -609,6 +609,178 b' var deleteNotification = function(url, n'
609 };
609 };
610
610
611
611
612 /** MEMBERS AUTOCOMPLETE WIDGET **/
613
614 var MembersAutoComplete = function (users_list, groups_list, group_lbl, members_lbl) {
615 var myUsers = users_list;
616 var myGroups = groups_list;
617
618 // Define a custom search function for the DataSource of users
619 var matchUsers = function (sQuery) {
620 // Case insensitive matching
621 var query = sQuery.toLowerCase();
622 var i = 0;
623 var l = myUsers.length;
624 var matches = [];
625
626 // Match against each name of each contact
627 for (; i < l; i++) {
628 contact = myUsers[i];
629 if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
630 matches[matches.length] = contact;
631 }
632 }
633 return matches;
634 };
635
636 // Define a custom search function for the DataSource of usersGroups
637 var matchGroups = function (sQuery) {
638 // Case insensitive matching
639 var query = sQuery.toLowerCase();
640 var i = 0;
641 var l = myGroups.length;
642 var matches = [];
643
644 // Match against each name of each contact
645 for (; i < l; i++) {
646 matched_group = myGroups[i];
647 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
648 matches[matches.length] = matched_group;
649 }
650 }
651 return matches;
652 };
653
654 //match all
655 var matchAll = function (sQuery) {
656 u = matchUsers(sQuery);
657 g = matchGroups(sQuery);
658 return u.concat(g);
659 };
660
661 // DataScheme for members
662 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
663 memberDS.responseSchema = {
664 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
665 };
666
667 // DataScheme for owner
668 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
669 ownerDS.responseSchema = {
670 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
671 };
672
673 // Instantiate AutoComplete for perms
674 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
675 membersAC.useShadow = false;
676 membersAC.resultTypeList = false;
677
678 // Instantiate AutoComplete for owner
679 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
680 ownerAC.useShadow = false;
681 ownerAC.resultTypeList = false;
682
683
684 // Helper highlight function for the formatter
685 var highlightMatch = function (full, snippet, matchindex) {
686 return full.substring(0, matchindex)
687 + "<span class='match'>"
688 + full.substr(matchindex, snippet.length)
689 + "</span>" + full.substring(matchindex + snippet.length);
690 };
691
692 // Custom formatter to highlight the matching letters
693 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
694 var query = sQuery.toLowerCase();
695 var _gravatar = function(res, em, group){
696 if (group !== undefined){
697 em = '/images/icons/group.png'
698 }
699 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
700 return tmpl.format(em,res)
701 }
702 // group
703 if (oResultData.grname != undefined) {
704 var grname = oResultData.grname;
705 var grmembers = oResultData.grmembers;
706 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
707 var grprefix = "{0}: ".format(group_lbl);
708 var grsuffix = " (" + grmembers + " )";
709 var grsuffix = " ({0} {1})".format(grmembers, members_lbl);
710
711 if (grnameMatchIndex > -1) {
712 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
713 }
714 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
715 // Users
716 } else if (oResultData.fname != undefined) {
717 var fname = oResultData.fname,
718 lname = oResultData.lname,
719 nname = oResultData.nname || "",
720 // Guard against null value
721 fnameMatchIndex = fname.toLowerCase().indexOf(query),
722 lnameMatchIndex = lname.toLowerCase().indexOf(query),
723 nnameMatchIndex = nname.toLowerCase().indexOf(query),
724 displayfname, displaylname, displaynname;
725
726 if (fnameMatchIndex > -1) {
727 displayfname = highlightMatch(fname, query, fnameMatchIndex);
728 } else {
729 displayfname = fname;
730 }
731
732 if (lnameMatchIndex > -1) {
733 displaylname = highlightMatch(lname, query, lnameMatchIndex);
734 } else {
735 displaylname = lname;
736 }
737
738 if (nnameMatchIndex > -1) {
739 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
740 } else {
741 displaynname = nname ? "(" + nname + ")" : "";
742 }
743
744 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
745 } else {
746 return '';
747 }
748 };
749 membersAC.formatResult = custom_formatter;
750 ownerAC.formatResult = custom_formatter;
751
752 var myHandler = function (sType, aArgs) {
753
754 var myAC = aArgs[0]; // reference back to the AC instance
755 var elLI = aArgs[1]; // reference to the selected LI element
756 var oData = aArgs[2]; // object literal of selected item's result data
757 //fill the autocomplete with value
758 if (oData.nname != undefined) {
759 //users
760 myAC.getInputEl().value = oData.nname;
761 YUD.get('perm_new_member_type').value = 'user';
762 } else {
763 //groups
764 myAC.getInputEl().value = oData.grname;
765 YUD.get('perm_new_member_type').value = 'users_group';
766 }
767 };
768
769 membersAC.itemSelectEvent.subscribe(myHandler);
770 if(ownerAC.itemSelectEvent){
771 ownerAC.itemSelectEvent.subscribe(myHandler);
772 }
773
774 return {
775 memberDS: memberDS,
776 ownerDS: ownerDS,
777 membersAC: membersAC,
778 ownerAC: ownerAC,
779 };
780 }
781
782
783
612 /**
784 /**
613 * QUICK REPO MENU
785 * QUICK REPO MENU
614 */
786 */
@@ -700,6 +872,19 b' var nameSort = function(a, b, desc, fiel'
700 return compState;
872 return compState;
701 };
873 };
702
874
875 var permNameSort = function(a, b, desc, field) {
876 var a_ = fromHTML(a.getData(field));
877 var b_ = fromHTML(b.getData(field));
878 // extract name from table
879
880 a_ = a_.children[0].innerHTML;
881 b_ = b_.children[0].innerHTML;
882
883 var comp = YAHOO.util.Sort.compare;
884 var compState = comp(a_, b_, desc);
885 return compState;
886 };
887
703 var groupNameSort = function(a, b, desc, field) {
888 var groupNameSort = function(a, b, desc, field) {
704 var a_ = fromHTML(a.getData(field));
889 var a_ = fromHTML(a.getData(field));
705 var b_ = fromHTML(b.getData(field));
890 var b_ = fromHTML(b.getData(field));
@@ -40,7 +40,14 b''
40 </ul>
40 </ul>
41 </%def>
41 </%def>
42
42
43 <%def name="repo_name(name,rtype,private,fork_of)">
43 <%def name="repo_name(name,rtype,private,fork_of,short_name=False, admin=False)">
44 <%
45 def get_name(name,short_name=short_name):
46 if short_name:
47 return name.split('/')[-1]
48 else:
49 return name
50 %>
44 <div style="white-space: nowrap">
51 <div style="white-space: nowrap">
45 ##TYPE OF REPO
52 ##TYPE OF REPO
46 %if h.is_hg(rtype):
53 %if h.is_hg(rtype):
@@ -57,7 +64,11 b''
57 %endif
64 %endif
58
65
59 ##NAME
66 ##NAME
60 ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")}
67 %if admin:
68 ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")}
69 %else:
70 ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")}
71 %endif
61 %if fork_of:
72 %if fork_of:
62 <a href="${h.url('summary_home',repo_name=fork_of)}">
73 <a href="${h.url('summary_home',repo_name=fork_of)}">
63 <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
74 <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
@@ -21,6 +21,7 b''
21 </div>
21 </div>
22 <div class="input">
22 <div class="input">
23 ${h.text('clone_uri',class_="small")}
23 ${h.text('clone_uri',class_="small")}
24 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
24 </div>
25 </div>
25 </div>
26 </div>
26 <div class="field">
27 <div class="field">
@@ -28,7 +29,8 b''
28 <label for="repo_group">${_('Repository group')}:</label>
29 <label for="repo_group">${_('Repository group')}:</label>
29 </div>
30 </div>
30 <div class="input">
31 <div class="input">
31 ${h.select('repo_group','',c.repo_groups,class_="medium")}
32 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
33 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
32 </div>
34 </div>
33 </div>
35 </div>
34 <div class="field">
36 <div class="field">
@@ -37,6 +39,7 b''
37 </div>
39 </div>
38 <div class="input">
40 <div class="input">
39 ${h.select('repo_type','hg',c.backends,class_="small")}
41 ${h.select('repo_type','hg',c.backends,class_="small")}
42 <span class="help-block">${_('Type of repository to create.')}</span>
40 </div>
43 </div>
41 </div>
44 </div>
42 <div class="field">
45 <div class="field">
@@ -44,15 +47,17 b''
44 <label for="description">${_('Description')}:</label>
47 <label for="description">${_('Description')}:</label>
45 </div>
48 </div>
46 <div class="textarea text-area editor">
49 <div class="textarea text-area editor">
47 ${h.textarea('description',cols=23,rows=5)}
50 ${h.textarea('description')}
51 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
48 </div>
52 </div>
49 </div>
53 </div>
50 <div class="field">
54 <div class="field">
51 <div class="label label-checkbox">
55 <div class="label label-checkbox">
52 <label for="private">${_('Private')}:</label>
56 <label for="private">${_('Private repository')}:</label>
53 </div>
57 </div>
54 <div class="checkboxes">
58 <div class="checkboxes">
55 ${h.checkbox('private',value="True")}
59 ${h.checkbox('private',value="True")}
60 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
56 </div>
61 </div>
57 </div>
62 </div>
58 <div class="buttons">
63 <div class="buttons">
@@ -41,6 +41,7 b''
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="medium")}
43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 </div>
45 </div>
45 </div>
46 </div>
46 <div class="field">
47 <div class="field">
@@ -49,6 +50,7 b''
49 </div>
50 </div>
50 <div class="input">
51 <div class="input">
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
52 </div>
54 </div>
53 </div>
55 </div>
54 <div class="field">
56 <div class="field">
@@ -64,16 +66,18 b''
64 <label for="description">${_('Description')}:</label>
66 <label for="description">${_('Description')}:</label>
65 </div>
67 </div>
66 <div class="textarea text-area editor">
68 <div class="textarea text-area editor">
67 ${h.textarea('description',cols=23,rows=5)}
69 ${h.textarea('description')}
70 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
68 </div>
71 </div>
69 </div>
72 </div>
70
73
71 <div class="field">
74 <div class="field">
72 <div class="label label-checkbox">
75 <div class="label label-checkbox">
73 <label for="private">${_('Private')}:</label>
76 <label for="private">${_('Private repository')}:</label>
74 </div>
77 </div>
75 <div class="checkboxes">
78 <div class="checkboxes">
76 ${h.checkbox('private',value="True")}
79 ${h.checkbox('private',value="True")}
80 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
77 </div>
81 </div>
78 </div>
82 </div>
79 <div class="field">
83 <div class="field">
@@ -82,6 +86,7 b''
82 </div>
86 </div>
83 <div class="checkboxes">
87 <div class="checkboxes">
84 ${h.checkbox('enable_statistics',value="True")}
88 ${h.checkbox('enable_statistics',value="True")}
89 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
85 </div>
90 </div>
86 </div>
91 </div>
87 <div class="field">
92 <div class="field">
@@ -90,15 +95,17 b''
90 </div>
95 </div>
91 <div class="checkboxes">
96 <div class="checkboxes">
92 ${h.checkbox('enable_downloads',value="True")}
97 ${h.checkbox('enable_downloads',value="True")}
98 <span class="help-block">${_('Enable download menu on summary page.')}</span>
93 </div>
99 </div>
94 </div>
100 </div>
95 <div class="field">
101 <div class="field">
96 <div class="label">
102 <div class="label">
97 <label for="user">${_('Owner')}:</label>
103 <label for="user">${_('Owner')}:</label>
98 </div>
104 </div>
99 <div class="input input-small ac">
105 <div class="input input-medium ac">
100 <div class="perm_ac">
106 <div class="perm_ac">
101 ${h.text('user',class_='yui-ac-input')}
107 ${h.text('user',class_='yui-ac-input')}
108 <span class="help-block">${_('Change owner of this repository.')}</span>
102 <div id="owner_container"></div>
109 <div id="owner_container"></div>
103 </div>
110 </div>
104 </div>
111 </div>
@@ -25,7 +25,7 b''
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 <td style="white-space: nowrap;">
27 <td style="white-space: nowrap;">
28 <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
29 </td>
29 </td>
30 <td>
30 <td>
31 %if r2p.user.username !='default':
31 %if r2p.user.username !='default':
@@ -46,7 +46,7 b''
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 <td style="white-space: nowrap;">
48 <td style="white-space: nowrap;">
49 <img style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
50 </td>
50 </td>
51 <td>
51 <td>
52 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
52 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
@@ -117,165 +117,12 b' YUE.onDOMReady(function () {'
117 YUD.setStyle('add_perm', 'opacity', '0.6');
117 YUD.setStyle('add_perm', 'opacity', '0.6');
118 YUD.setStyle('add_perm', 'cursor', 'default');
118 YUD.setStyle('add_perm', 'cursor', 'default');
119 });
119 });
120 MembersAutoComplete(
121 ${c.users_array|n},
122 ${c.users_groups_array|n},
123 "${_('Group')}",
124 "${_('members')}"
125 );
120 });
126 });
121
127
122 YAHOO.example.FnMultipleFields = function () {
123 var myUsers = ${c.users_array|n};
124 var myGroups = ${c.users_groups_array|n};
125
126 // Define a custom search function for the DataSource of users
127 var matchUsers = function (sQuery) {
128 // Case insensitive matching
129 var query = sQuery.toLowerCase();
130 var i = 0;
131 var l = myUsers.length;
132 var matches = [];
133
134 // Match against each name of each contact
135 for (; i < l; i++) {
136 contact = myUsers[i];
137 if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
138 matches[matches.length] = contact;
139 }
140 }
141 return matches;
142 };
143
144 // Define a custom search function for the DataSource of usersGroups
145 var matchGroups = function (sQuery) {
146 // Case insensitive matching
147 var query = sQuery.toLowerCase();
148 var i = 0;
149 var l = myGroups.length;
150 var matches = [];
151
152 // Match against each name of each contact
153 for (; i < l; i++) {
154 matched_group = myGroups[i];
155 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
156 matches[matches.length] = matched_group;
157 }
158 }
159 return matches;
160 };
161
162 //match all
163 var matchAll = function (sQuery) {
164 u = matchUsers(sQuery);
165 g = matchGroups(sQuery);
166 return u.concat(g);
167 };
168
169 // DataScheme for members
170 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
171 memberDS.responseSchema = {
172 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
173 };
174
175 // DataScheme for owner
176 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
177 ownerDS.responseSchema = {
178 fields: ["id", "fname", "lname", "nname"]
179 };
180
181 // Instantiate AutoComplete for perms
182 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
183 membersAC.useShadow = false;
184 membersAC.resultTypeList = false;
185
186 // Instantiate AutoComplete for owner
187 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
188 ownerAC.useShadow = false;
189 ownerAC.resultTypeList = false;
190
191
192 // Helper highlight function for the formatter
193 var highlightMatch = function (full, snippet, matchindex) {
194 return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
195 };
196
197 // Custom formatter to highlight the matching letters
198 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
199 var query = sQuery.toLowerCase();
200
201 if (oResultData.grname != undefined) {
202 var grname = oResultData.grname;
203 var grmembers = oResultData.grmembers;
204 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
205 var grprefix = "${_('Group')}: ";
206 var grsuffix = " (" + grmembers + " ${_('members')})";
207
208 if (grnameMatchIndex > -1) {
209 return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
210 }
211
212 return grprefix + oResultData.grname + grsuffix;
213 } else if (oResultData.fname != undefined) {
214
215 var fname = oResultData.fname,
216 lname = oResultData.lname,
217 nname = oResultData.nname || "",
218 // Guard against null value
219 fnameMatchIndex = fname.toLowerCase().indexOf(query),
220 lnameMatchIndex = lname.toLowerCase().indexOf(query),
221 nnameMatchIndex = nname.toLowerCase().indexOf(query),
222 displayfname, displaylname, displaynname;
223
224 if (fnameMatchIndex > -1) {
225 displayfname = highlightMatch(fname, query, fnameMatchIndex);
226 } else {
227 displayfname = fname;
228 }
229
230 if (lnameMatchIndex > -1) {
231 displaylname = highlightMatch(lname, query, lnameMatchIndex);
232 } else {
233 displaylname = lname;
234 }
235
236 if (nnameMatchIndex > -1) {
237 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
238 } else {
239 displaynname = nname ? "(" + nname + ")" : "";
240 }
241
242 return displayfname + " " + displaylname + " " + displaynname;
243 } else {
244 return '';
245 }
246 };
247 membersAC.formatResult = custom_formatter;
248 ownerAC.formatResult = custom_formatter;
249
250 var myHandler = function (sType, aArgs) {
251
252 var myAC = aArgs[0]; // reference back to the AC instance
253 var elLI = aArgs[1]; // reference to the selected LI element
254 var oData = aArgs[2]; // object literal of selected item's result data
255 //fill the autocomplete with value
256 if (oData.nname != undefined) {
257 //users
258 myAC.getInputEl().value = oData.nname;
259 YUD.get('perm_new_member_type').value = 'user';
260 } else {
261 //groups
262 myAC.getInputEl().value = oData.grname;
263 YUD.get('perm_new_member_type').value = 'users_group';
264 }
265
266 };
267
268 membersAC.itemSelectEvent.subscribe(myHandler);
269 if(ownerAC.itemSelectEvent){
270 ownerAC.itemSelectEvent.subscribe(myHandler);
271 }
272
273 return {
274 memberDS: memberDS,
275 ownerDS: ownerDS,
276 membersAC: membersAC,
277 ownerAC: ownerAC,
278 };
279 }();
280
281 </script>
128 </script>
@@ -48,7 +48,7 b''
48 ${dt.quick_menu(repo['name'])}
48 ${dt.quick_menu(repo['name'])}
49 </td>
49 </td>
50 <td class="reponame">
50 <td class="reponame">
51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'), admin=True)}
52 </td>
52 </td>
53 ##DESCRIPTION
53 ##DESCRIPTION
54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
@@ -15,7 +15,7 b''
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 <td style="white-space: nowrap;">
17 <td style="white-space: nowrap;">
18 <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
18 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
19 </td>
19 </td>
20 <td>
20 <td>
21 %if r2p.user.username !='default':
21 %if r2p.user.username !='default':
@@ -35,7 +35,7 b''
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 <td style="white-space: nowrap;">
37 <td style="white-space: nowrap;">
38 <img style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
38 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 </td>
39 </td>
40 <td>
40 <td>
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
@@ -68,7 +68,7 b''
68 </table>
68 </table>
69 <script type="text/javascript">
69 <script type="text/javascript">
70 function ajaxActionUser(user_id, field_id) {
70 function ajaxActionUser(user_id, field_id) {
71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}";
71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
72 var callback = {
72 var callback = {
73 success: function (o) {
73 success: function (o) {
74 var tr = YUD.get(String(field_id));
74 var tr = YUD.get(String(field_id));
@@ -83,7 +83,7 b' function ajaxActionUser(user_id, field_i'
83 };
83 };
84
84
85 function ajaxActionUsersGroup(users_group_id,field_id){
85 function ajaxActionUsersGroup(users_group_id,field_id){
86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}";
86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
87 var callback = {
87 var callback = {
88 success:function(o){
88 success:function(o){
89 var tr = YUD.get(String(field_id));
89 var tr = YUD.get(String(field_id));
@@ -106,165 +106,12 b' YUE.onDOMReady(function () {'
106 YUD.setStyle('add_perm', 'opacity', '0.6');
106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 YUD.setStyle('add_perm', 'cursor', 'default');
107 YUD.setStyle('add_perm', 'cursor', 'default');
108 });
108 });
109 MembersAutoComplete(
110 ${c.users_array|n},
111 ${c.users_groups_array|n},
112 "${_('Group')}",
113 "${_('members')}"
114 );
109 });
115 });
110
116
111 YAHOO.example.FnMultipleFields = function () {
112 var myUsers = ${c.users_array|n};
113 var myGroups = ${c.users_groups_array|n};
114
115 // Define a custom search function for the DataSource of users
116 var matchUsers = function (sQuery) {
117 // Case insensitive matching
118 var query = sQuery.toLowerCase();
119 var i = 0;
120 var l = myUsers.length;
121 var matches = [];
122
123 // Match against each name of each contact
124 for (; i < l; i++) {
125 contact = myUsers[i];
126 if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
127 matches[matches.length] = contact;
128 }
129 }
130 return matches;
131 };
132
133 // Define a custom search function for the DataSource of usersGroups
134 var matchGroups = function (sQuery) {
135 // Case insensitive matching
136 var query = sQuery.toLowerCase();
137 var i = 0;
138 var l = myGroups.length;
139 var matches = [];
140
141 // Match against each name of each contact
142 for (; i < l; i++) {
143 matched_group = myGroups[i];
144 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
145 matches[matches.length] = matched_group;
146 }
147 }
148 return matches;
149 };
150
151 //match all
152 var matchAll = function (sQuery) {
153 u = matchUsers(sQuery);
154 g = matchGroups(sQuery);
155 return u.concat(g);
156 };
157
158 // DataScheme for members
159 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
160 memberDS.responseSchema = {
161 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
162 };
163
164 // DataScheme for owner
165 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
166 ownerDS.responseSchema = {
167 fields: ["id", "fname", "lname", "nname"]
168 };
169
170 // Instantiate AutoComplete for perms
171 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
172 membersAC.useShadow = false;
173 membersAC.resultTypeList = false;
174
175 // Instantiate AutoComplete for owner
176 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
177 ownerAC.useShadow = false;
178 ownerAC.resultTypeList = false;
179
180
181 // Helper highlight function for the formatter
182 var highlightMatch = function (full, snippet, matchindex) {
183 return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
184 };
185
186 // Custom formatter to highlight the matching letters
187 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
188 var query = sQuery.toLowerCase();
189
190 if (oResultData.grname != undefined) {
191 var grname = oResultData.grname;
192 var grmembers = oResultData.grmembers;
193 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
194 var grprefix = "${_('Group')}: ";
195 var grsuffix = " (" + grmembers + " ${_('members')})";
196
197 if (grnameMatchIndex > -1) {
198 return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
199 }
200
201 return grprefix + oResultData.grname + grsuffix;
202 } else if (oResultData.fname != undefined) {
203
204 var fname = oResultData.fname,
205 lname = oResultData.lname,
206 nname = oResultData.nname || "",
207 // Guard against null value
208 fnameMatchIndex = fname.toLowerCase().indexOf(query),
209 lnameMatchIndex = lname.toLowerCase().indexOf(query),
210 nnameMatchIndex = nname.toLowerCase().indexOf(query),
211 displayfname, displaylname, displaynname;
212
213 if (fnameMatchIndex > -1) {
214 displayfname = highlightMatch(fname, query, fnameMatchIndex);
215 } else {
216 displayfname = fname;
217 }
218
219 if (lnameMatchIndex > -1) {
220 displaylname = highlightMatch(lname, query, lnameMatchIndex);
221 } else {
222 displaylname = lname;
223 }
224
225 if (nnameMatchIndex > -1) {
226 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
227 } else {
228 displaynname = nname ? "(" + nname + ")" : "";
229 }
230
231 return displayfname + " " + displaylname + " " + displaynname;
232 } else {
233 return '';
234 }
235 };
236 membersAC.formatResult = custom_formatter;
237 ownerAC.formatResult = custom_formatter;
238
239 var myHandler = function (sType, aArgs) {
240
241 var myAC = aArgs[0]; // reference back to the AC instance
242 var elLI = aArgs[1]; // reference to the selected LI element
243 var oData = aArgs[2]; // object literal of selected item's result data
244 //fill the autocomplete with value
245 if (oData.nname != undefined) {
246 //users
247 myAC.getInputEl().value = oData.nname;
248 YUD.get('perm_new_member_type').value = 'user';
249 } else {
250 //groups
251 myAC.getInputEl().value = oData.grname;
252 YUD.get('perm_new_member_type').value = 'users_group';
253 }
254
255 };
256
257 membersAC.itemSelectEvent.subscribe(myHandler);
258 if(ownerAC.itemSelectEvent){
259 ownerAC.itemSelectEvent.subscribe(myHandler);
260 }
261
262 return {
263 memberDS: memberDS,
264 ownerDS: ownerDS,
265 membersAC: membersAC,
266 ownerAC: ownerAC,
267 };
268 }();
269
270 </script>
117 </script>
@@ -17,5 +17,5 b''
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19 <%def name="main()">
19 <%def name="main()">
20 <%include file="/index_base.html" args="parent=self"/>
20 <%include file="/index_base.html" args="parent=self,short_repo_names=True"/>
21 </%def>
21 </%def>
@@ -44,7 +44,7 b''
44 <td>
44 <td>
45 <div style="white-space: nowrap">
45 <div style="white-space: nowrap">
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
47 ${h.link_to(h.literal(' &raquo; '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
47 ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))),url('edit_repos_group',id=gr.group_id))}
48 </div>
48 </div>
49 </td>
49 </td>
50 <td>${gr.group_description}</td>
50 <td>${gr.group_description}</td>
@@ -113,52 +113,45 b''
113 <div class="title">
113 <div class="title">
114 <h5>
114 <h5>
115 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
115 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
116 ${_('My repositories')}
116 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_perms" class="link-white" href="#perms">${_('My permissions')}</a>
117 </h5>
117 </h5>
118 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
118 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
119 <ul class="links">
119 <ul class="links">
120 <li>
120 <li>
121 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
121 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
122 </li>
122 </li>
123 </ul>
123 </ul>
124 %endif
124 %endif
125 </div>
125 </div>
126 <!-- end box / title -->
126 <!-- end box / title -->
127 <div class="table">
127 <div id="my" class="table">
128 <table>
128 <div id='repos_list_wrap' class="yui-skin-sam">
129 <table id="repos_list">
129 <thead>
130 <thead>
130 <tr>
131 <tr>
132 <th></th>
131 <th class="left">${_('Name')}</th>
133 <th class="left">${_('Name')}</th>
132 <th class="left">${_('revision')}</th>
134 <th class="left">${_('Revision')}</th>
133 <th colspan="2" class="left">${_('action')}</th>
135 <th class="left">${_('Action')}</th>
136 <th class="left">${_('Action')}</th>
134 </thead>
137 </thead>
135 <tbody>
138 <tbody>
139 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
136 %if c.user_repos:
140 %if c.user_repos:
137 %for repo in c.user_repos:
141 %for repo in c.user_repos:
138 <tr>
142 <tr>
139 <td>
143 ##QUICK MENU
140 %if h.is_hg(repo['dbrepo']['repo_type']):
144 <td class="quick_repo_menu">
141 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
145 ${dt.quick_menu(repo['name'])}
142 %elif h.is_git(repo['dbrepo']['repo_type']):
146 </td>
143 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
147 ##REPO NAME AND ICONS
144 %else:
148 <td class="reponame">
145
149 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
146 %endif
150 </td>
147 %if repo['dbrepo']['private']:
151 ##LAST REVISION
148 <img class="icon" alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
152 <td>
149 %else:
153 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
150 <img class="icon" alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
154 </td>
151 %endif
152
153 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
154 %if repo['dbrepo_fork']:
155 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
156 <img class="icon" alt="${_('public')}"
157 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
158 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
159 %endif
160 </td>
161 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
162 <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>
155 <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>
163 <td>
156 <td>
164 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
157 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
@@ -177,14 +170,144 b''
177 %endif
170 %endif
178 </tbody>
171 </tbody>
179 </table>
172 </table>
173 </div>
174 </div>
175 <div id="perms" class="table" style="display:none">
176 %for section in sorted(c.rhodecode_user.permissions.keys()):
177 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
178
179 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
180 <table id="tbl_list_${section}">
181 <thead>
182 <tr>
183 <th class="left">${_('Name')}</th>
184 <th class="left">${_('Permission')}</th>
185 </thead>
186 <tbody>
187 %for k in c.rhodecode_user.permissions[section]:
188 <%
189 if section != 'global':
190 section_perm = c.rhodecode_user.permissions[section].get(k)
191 _perm = section_perm.split('.')[-1]
192 else:
193 _perm = section_perm = None
194 %>
195 %if _perm not in ['none']:
196 <tr>
197 <td>
198 %if section == 'repositories':
199 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
200 %elif section == 'repositories_groups':
201 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
202 %else:
203 ${k}
204 %endif
205 </td>
206 <td>
207 %if section == 'global':
208 ${h.bool2icon(True)}
209 %else:
210 <span class="perm_tag ${_perm}">${section_perm}</span>
211 %endif
212 </td>
213 </tr>
214 %endif
215 %endfor
216 </tbody>
217 </table>
218 </div>
219 %endfor
180 </div>
220 </div>
181 </div>
221 </div>
182 <script type="text/javascript">
222 <script type="text/javascript">
183 var nodes = YUQ('div.table tr td a.repo_name');
223 var filter_activate = function(){
184 var target = 'q_filter';
224 var nodes = YUQ('#my tr td a.repo_name');
185 var func = function(node){
225 var func = function(node){
186 return node.parentNode.parentNode;
226 return node.parentNode.parentNode.parentNode.parentNode;
227 }
228 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
187 }
229 }
188 q_filter(target,nodes,func);
230
231 YUE.on('show_my','click',function(e){
232 YUD.setStyle('perms','display','none');
233 YUD.setStyle('my','display','');
234 YUD.get('q_filter').removeAttribute('disabled');
235 filter_activate();
236 YUE.preventDefault(e);
237 })
238 YUE.on('show_perms','click',function(e){
239 YUD.setStyle('my','display','none');
240 YUD.setStyle('perms','display','');
241 YUD.setAttribute('q_filter','disabled','disabled');
242 YUE.preventDefault(e);
243 })
244
245
246 // main table sorting
247 var myColumnDefs = [
248 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
249 {key:"name",label:"${_('Name')}",sortable:true,
250 sortOptions: { sortFunction: nameSort }},
251 {key:"tip",label:"${_('Tip')}",sortable:true,
252 sortOptions: { sortFunction: revisionSort }},
253 {key:"action1",label:"",sortable:false},
254 {key:"action2",label:"",sortable:false},
255 ];
256
257 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
258 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
259 myDataSource.responseSchema = {
260 fields: [
261 {key:"menu"},
262 {key:"name"},
263 {key:"tip"},
264 {key:"action1"},
265 {key:"action2"},
266 ]
267 };
268 var trans_defs = {
269 sortedBy:{key:"name",dir:"asc"},
270 MSG_SORTASC:"${_('Click to sort ascending')}",
271 MSG_SORTDESC:"${_('Click to sort descending')}",
272 MSG_EMPTY:"${_('No records found.')}",
273 MSG_ERROR:"${_('Data error.')}",
274 MSG_LOADING:"${_('Loading...')}",
275 }
276 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
277 myDataTable.subscribe('postRenderEvent',function(oArgs) {
278 tooltip_activate();
279 quick_repo_menu();
280 filter_activate();
281 });
282
283 var permsColumnDefs = [
284 {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
285 {key:"perm",label:"${_('Permission')}",sortable:false,},
286 ];
287
288 // perms repos table
289 var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
290 myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
291 myDataSource2.responseSchema = {
292 fields: [
293 {key:"name"},
294 {key:"perm"},
295 ]
296 };
297
298 new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
299
300 //perms groups table
301 var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
302 myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
303 myDataSource3.responseSchema = {
304 fields: [
305 {key:"name"},
306 {key:"perm"},
307 ]
308 };
309
310 new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
311
189 </script>
312 </script>
190 </%def>
313 </%def>
@@ -37,7 +37,7 b''
37 %for cnt,u_group in enumerate(c.users_groups_list):
37 %for cnt,u_group in enumerate(c.users_groups_list):
38 <tr class="parity${cnt%2}">
38 <tr class="parity${cnt%2}">
39 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
39 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
40 <td><span class="tooltip" title="${', '.join([x.user.username for x in u_group.members[:50]])}">${len(u_group.members)}</span></td>
40 <td><span class="tooltip" title="${', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]]))}">${len(u_group.members)}</span></td>
41 <td>${h.bool2icon(u_group.users_group_active)}</td>
41 <td>${h.bool2icon(u_group.users_group_active)}</td>
42 <td>
42 <td>
43 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
43 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
@@ -1,5 +1,5 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html>
3 <html xmlns="http://www.w3.org/1999/xhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${self.title()}</title>
5 <title>${self.title()}</title>
@@ -37,7 +37,7 b''
37 ## JAVASCRIPT ##
37 ## JAVASCRIPT ##
38 <%def name="js()">
38 <%def name="js()">
39 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
39 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
40 <!--[if IE]>
40 <!--[if lt IE 9]>
41 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
41 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
42 <![endif]-->
42 <![endif]-->
43 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
43 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
@@ -130,6 +130,17 b''
130 ${self.js()}
130 ${self.js()}
131 </head>
131 </head>
132 <body id="body">
132 <body id="body">
133 ${next.body()}
133 ## IE hacks
134 <!--[if IE 7]>
135 <script>YUD.addClass(document.body,'ie7')</script>
136 <![endif]-->
137 <!--[if IE 8]>
138 <script>YUD.addClass(document.body,'ie8')</script>
139 <![endif]-->
140 <!--[if IE 9]>
141 <script>YUD.addClass(document.body,'ie9')</script>
142 <![endif]-->
143
144 ${next.body()}
134 </body>
145 </body>
135 </html>
146 </html>
@@ -93,11 +93,11 b''
93 %endif
93 %endif
94 %if h.is_hg(c.rhodecode_repo) and cs.branch:
94 %if h.is_hg(c.rhodecode_repo) and cs.branch:
95 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
95 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
96 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
96 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
97 %endif
97 %endif
98 %for tag in cs.tags:
98 %for tag in cs.tags:
99 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
99 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
100 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
100 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
101 %endfor
101 %endfor
102 </span>
102 </span>
103 </div>
103 </div>
@@ -36,8 +36,8 b''
36 <div class="diff-actions">
36 <div class="diff-actions">
37 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
37 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
38 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
38 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
39 ${c.ignorews_url()}
39 ${c.ignorews_url(request.GET)}
40 ${c.context_url()}
40 ${c.context_url(request.GET)}
41 </div>
41 </div>
42 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
42 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
43 </div>
43 </div>
@@ -91,14 +91,14 b''
91 </div>
91 </div>
92 </div>
92 </div>
93 <span>
93 <span>
94 ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
94 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
95 </span>
95 </span>
96 <div class="cs_files">
96 <div class="cs_files">
97 %for change,filenode,diff,cs1,cs2,stat in c.changes:
97 %for change,filenode,diff,cs1,cs2,stat in c.changes:
98 <div class="cs_${change}">
98 <div class="cs_${change}">
99 <div class="node">
99 <div class="node">
100 %if change != 'removed':
100 %if change != 'removed':
101 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
101 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
102 %else:
102 %else:
103 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
103 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
104 %endif
104 %endif
@@ -7,7 +7,7 b''
7
7
8 %for change,filenode,diff,cs1,cs2,stat in changes:
8 %for change,filenode,diff,cs1,cs2,stat in changes:
9 %if change !='removed':
9 %if change !='removed':
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;height:90px;margin-top:-60px"></div>
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;margin-top:25px"></div>
11 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm">
11 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm">
12 <div class="code-header">
12 <div class="code-header">
13 <div class="changeset_header">
13 <div class="changeset_header">
@@ -19,8 +19,8 b''
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
22 ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}
22 ${c.ignorews_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
23 ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}
23 ${c.context_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
24 </div>
24 </div>
25 <span style="float:right;margin-top:-3px">
25 <span style="float:right;margin-top:-3px">
26 <label>
26 <label>
@@ -4,5 +4,5 b''
4 <%def name="breadcrumbs()"></%def>
4 <%def name="breadcrumbs()"></%def>
5 <%def name="page_nav()">${self.menu('home')}</%def>
5 <%def name="page_nav()">${self.menu('home')}</%def>
6 <%def name="main()">
6 <%def name="main()">
7 <%include file="index_base.html" args="parent=self"/>
7 <%include file="index_base.html" args="parent=self"/>
8 </%def>
8 </%def>
@@ -9,7 +9,11 b''
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
10 <ul class="links">
11 <li>
11 <li>
12 %if c.group:
13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 %else:
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 %endif
13 </li>
17 </li>
14 </ul>
18 </ul>
15 %endif
19 %endif
@@ -77,7 +81,7 b''
77 </td>
81 </td>
78 ##REPO NAME AND ICONS
82 ##REPO NAME AND ICONS
79 <td class="reponame">
83 <td class="reponame">
80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
84 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
81 </td>
85 </td>
82 ##DESCRIPTION
86 ##DESCRIPTION
83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
87 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
@@ -115,7 +119,7 b''
115 </div>
119 </div>
116 </div>
120 </div>
117 <script>
121 <script>
118 YUD.get('repo_count').innerHTML = ${cnt+1};
122 YUD.get('repo_count').innerHTML = ${cnt};
119 var func = function(node){
123 var func = function(node){
120 return node.parentNode.parentNode.parentNode.parentNode;
124 return node.parentNode.parentNode.parentNode.parentNode;
121 }
125 }
@@ -48,7 +48,7 b''
48 <tr>
48 <tr>
49 <th></th>
49 <th></th>
50 <th class="left">${_('Name')}</th>
50 <th class="left">${_('Name')}</th>
51 <th class="left">${_('Tip')}</th>
51 <th class="left">${_('Revision')}</th>
52 <th class="left">${_('Action')}</th>
52 <th class="left">${_('Action')}</th>
53 <th class="left">${_('Action')}</th>
53 <th class="left">${_('Action')}</th>
54 </thead>
54 </thead>
@@ -34,12 +34,22 b''
34 ${h.text('repo_name',class_="small")}
34 ${h.text('repo_name',class_="small")}
35 </div>
35 </div>
36 </div>
36 </div>
37 <div class="field">
38 <div class="label">
39 <label for="clone_uri">${_('Clone uri')}:</label>
40 </div>
41 <div class="input">
42 ${h.text('clone_uri',class_="medium")}
43 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 </div>
45 </div>
37 <div class="field">
46 <div class="field">
38 <div class="label">
47 <div class="label">
39 <label for="repo_group">${_('Repository group')}:</label>
48 <label for="repo_group">${_('Repository group')}:</label>
40 </div>
49 </div>
41 <div class="input">
50 <div class="input">
42 ${h.select('repo_group','',c.repo_groups,class_="medium")}
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
43 </div>
53 </div>
44 </div>
54 </div>
45 <div class="field">
55 <div class="field">
@@ -47,16 +57,18 b''
47 <label for="description">${_('Description')}:</label>
57 <label for="description">${_('Description')}:</label>
48 </div>
58 </div>
49 <div class="textarea text-area editor">
59 <div class="textarea text-area editor">
50 ${h.textarea('description',cols=23,rows=5)}
60 ${h.textarea('description')}
61 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
51 </div>
62 </div>
52 </div>
63 </div>
53
64
54 <div class="field">
65 <div class="field">
55 <div class="label label-checkbox">
66 <div class="label label-checkbox">
56 <label for="private">${_('Private')}:</label>
67 <label for="private">${_('Private repository')}:</label>
57 </div>
68 </div>
58 <div class="checkboxes">
69 <div class="checkboxes">
59 ${h.checkbox('private',value="True")}
70 ${h.checkbox('private',value="True")}
71 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
60 </div>
72 </div>
61 </div>
73 </div>
62
74
@@ -2,7 +2,8 b' from rhodecode.tests import *'
2 from rhodecode.model.db import ChangesetComment, Notification, User, \
2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 UserNotification
3 UserNotification
4
4
5 class TestChangeSetCommentrController(TestController):
5
6 class TestChangeSetCommentsController(TestController):
6
7
7 def setUp(self):
8 def setUp(self):
8 for x in ChangesetComment.query().all():
9 for x in ChangesetComment.query().all():
@@ -27,7 +28,7 b' class TestChangeSetCommentrController(Te'
27 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
28 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
28 text = u'CommentOnRevision'
29 text = u'CommentOnRevision'
29
30
30 params = {'text':text}
31 params = {'text': text}
31 response = self.app.post(url(controller='changeset', action='comment',
32 response = self.app.post(url(controller='changeset', action='comment',
32 repo_name=HG_REPO, revision=rev),
33 repo_name=HG_REPO, revision=rev),
33 params=params)
34 params=params)
@@ -42,13 +43,18 b' class TestChangeSetCommentrController(Te'
42 self.assertTrue('''<div class="comments-number">%s '''
43 self.assertTrue('''<div class="comments-number">%s '''
43 '''comment(s) (0 inline)</div>''' % 1 in response.body)
44 '''comment(s) (0 inline)</div>''' % 1 in response.body)
44
45
46 self.assertEqual(Notification.query().count(), 1)
47 self.assertEqual(ChangesetComment.query().count(), 1)
45
48
46 self.assertEqual(Notification.query().count(), 1)
47 notification = Notification.query().all()[0]
49 notification = Notification.query().all()[0]
48
50
49 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
51 ID = ChangesetComment.query().first().comment_id
50 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
52 self.assertEqual(notification.type_,
51 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
53 Notification.TYPE_CHANGESET_COMMENT)
54 sbj = (u'/vcs_test_hg/changeset/'
55 '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
56 print "%s vs %s" % (sbj, notification.subject)
57 self.assertTrue(sbj in notification.subject)
52
58
53 def test_create_inline(self):
59 def test_create_inline(self):
54 self.log_user()
60 self.log_user()
@@ -57,7 +63,7 b' class TestChangeSetCommentrController(Te'
57 f_path = 'vcs/web/simplevcs/views/repository.py'
63 f_path = 'vcs/web/simplevcs/views/repository.py'
58 line = 'n1'
64 line = 'n1'
59
65
60 params = {'text':text, 'f_path':f_path, 'line':line}
66 params = {'text': text, 'f_path': f_path, 'line': line}
61 response = self.app.post(url(controller='changeset', action='comment',
67 response = self.app.post(url(controller='changeset', action='comment',
62 repo_name=HG_REPO, revision=rev),
68 repo_name=HG_REPO, revision=rev),
63 params=params)
69 params=params)
@@ -76,11 +82,16 b' class TestChangeSetCommentrController(Te'
76 '''repositorypy">''' in response.body)
82 '''repositorypy">''' in response.body)
77
83
78 self.assertEqual(Notification.query().count(), 1)
84 self.assertEqual(Notification.query().count(), 1)
79 notification = Notification.query().all()[0]
85 self.assertEqual(ChangesetComment.query().count(), 1)
80
86
81 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
87 notification = Notification.query().all()[0]
82 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
88 ID = ChangesetComment.query().first().comment_id
83 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
89 self.assertEqual(notification.type_,
90 Notification.TYPE_CHANGESET_COMMENT)
91 sbj = (u'/vcs_test_hg/changeset/'
92 '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
93 print "%s vs %s" % (sbj, notification.subject)
94 self.assertTrue(sbj in notification.subject)
84
95
85 def test_create_with_mention(self):
96 def test_create_with_mention(self):
86 self.log_user()
97 self.log_user()
@@ -103,7 +114,6 b' class TestChangeSetCommentrController(Te'
103 self.assertTrue('''<div class="comments-number">%s '''
114 self.assertTrue('''<div class="comments-number">%s '''
104 '''comment(s) (0 inline)</div>''' % 1 in response.body)
115 '''comment(s) (0 inline)</div>''' % 1 in response.body)
105
116
106
107 self.assertEqual(Notification.query().count(), 2)
117 self.assertEqual(Notification.query().count(), 2)
108 users = [x.user.username for x in UserNotification.query().all()]
118 users = [x.user.username for x in UserNotification.query().all()]
109
119
@@ -115,7 +125,7 b' class TestChangeSetCommentrController(Te'
115 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
125 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
116 text = u'CommentOnRevision'
126 text = u'CommentOnRevision'
117
127
118 params = {'text':text}
128 params = {'text': text}
119 response = self.app.post(url(controller='changeset', action='comment',
129 response = self.app.post(url(controller='changeset', action='comment',
120 repo_name=HG_REPO, revision=rev),
130 repo_name=HG_REPO, revision=rev),
121 params=params)
131 params=params)
@@ -124,7 +134,6 b' class TestChangeSetCommentrController(Te'
124 self.assertEqual(len(comments), 1)
134 self.assertEqual(len(comments), 1)
125 comment_id = comments[0].comment_id
135 comment_id = comments[0].comment_id
126
136
127
128 self.app.delete(url(controller='changeset',
137 self.app.delete(url(controller='changeset',
129 action='delete_comment',
138 action='delete_comment',
130 repo_name=HG_REPO,
139 repo_name=HG_REPO,
@@ -1,10 +1,11 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 from rhodecode.tests import *
2 from rhodecode.tests import *
3 from rhodecode.model.db import User, Notification
3 from rhodecode.model.db import User, Notification
4 from rhodecode.lib import generate_api_key
4 from rhodecode.lib.utils2 import generate_api_key
5 from rhodecode.lib.auth import check_password
5 from rhodecode.lib.auth import check_password
6 from rhodecode.model.meta import Session
6 from rhodecode.model.meta import Session
7
7
8
8 class TestLoginController(TestController):
9 class TestLoginController(TestController):
9
10
10 def tearDown(self):
11 def tearDown(self):
@@ -65,22 +65,20 b' TEST_URLS += ['
65
65
66 class TestLibs(unittest.TestCase):
66 class TestLibs(unittest.TestCase):
67
67
68
69 def test_uri_filter(self):
68 def test_uri_filter(self):
70 from rhodecode.lib import uri_filter
69 from rhodecode.lib.utils2 import uri_filter
71
70
72 for url in TEST_URLS:
71 for url in TEST_URLS:
73 self.assertEqual(uri_filter(url[0]), url[1])
72 self.assertEqual(uri_filter(url[0]), url[1])
74
73
75 def test_credentials_filter(self):
74 def test_credentials_filter(self):
76 from rhodecode.lib import credentials_filter
75 from rhodecode.lib.utils2 import credentials_filter
77
76
78 for url in TEST_URLS:
77 for url in TEST_URLS:
79 self.assertEqual(credentials_filter(url[0]), url[2])
78 self.assertEqual(credentials_filter(url[0]), url[2])
80
79
81
82 def test_str2bool(self):
80 def test_str2bool(self):
83 from rhodecode.lib import str2bool
81 from rhodecode.lib.utils2 import str2bool
84 test_cases = [
82 test_cases = [
85 ('t', True),
83 ('t', True),
86 ('true', True),
84 ('true', True),
@@ -103,9 +101,8 b' class TestLibs(unittest.TestCase):'
103 for case in test_cases:
101 for case in test_cases:
104 self.assertEqual(str2bool(case[0]), case[1])
102 self.assertEqual(str2bool(case[0]), case[1])
105
103
106
107 def test_mention_extractor(self):
104 def test_mention_extractor(self):
108 from rhodecode.lib import extract_mentioned_users
105 from rhodecode.lib.utils2 import extract_mentioned_users
109 sample = ("@first hi there @marcink here's my email marcin@email.com "
106 sample = ("@first hi there @marcink here's my email marcin@email.com "
110 "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
107 "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
111 "@MARCIN @maRCiN @2one_more22")
108 "@MARCIN @maRCiN @2one_more22")
@@ -5,7 +5,7 b' from rhodecode.tests import *'
5 from rhodecode.model.repos_group import ReposGroupModel
5 from rhodecode.model.repos_group import ReposGroupModel
6 from rhodecode.model.repo import RepoModel
6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 UsersGroup, UsersGroupMember, Permission
8 UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm
9 from sqlalchemy.exc import IntegrityError
9 from sqlalchemy.exc import IntegrityError
10 from rhodecode.model.user import UserModel
10 from rhodecode.model.user import UserModel
11
11
@@ -430,6 +430,11 b' class TestPermissions(unittest.TestCase)'
430 username=u'u1', password=u'qweqwe',
430 username=u'u1', password=u'qweqwe',
431 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
431 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
432 )
432 )
433 self.u2 = UserModel().create_or_update(
434 username=u'u2', password=u'qweqwe',
435 email=u'u2@rhodecode.org', name=u'u2', lastname=u'u2'
436 )
437 self.anon = User.get_by_username('default')
433 self.a1 = UserModel().create_or_update(
438 self.a1 = UserModel().create_or_update(
434 username=u'a1', password=u'qweqwe',
439 username=u'a1', password=u'qweqwe',
435 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
440 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
@@ -437,7 +442,10 b' class TestPermissions(unittest.TestCase)'
437 Session.commit()
442 Session.commit()
438
443
439 def tearDown(self):
444 def tearDown(self):
445 if hasattr(self, 'test_repo'):
446 RepoModel().delete(repo=self.test_repo)
440 UserModel().delete(self.u1)
447 UserModel().delete(self.u1)
448 UserModel().delete(self.u2)
441 UserModel().delete(self.a1)
449 UserModel().delete(self.a1)
442 if hasattr(self, 'g1'):
450 if hasattr(self, 'g1'):
443 ReposGroupModel().delete(self.g1.group_id)
451 ReposGroupModel().delete(self.g1.group_id)
@@ -578,3 +586,130 b' class TestPermissions(unittest.TestCase)'
578 new_perm_h)
586 new_perm_h)
579 self.assertEqual(u1_auth.permissions['repositories_groups'],
587 self.assertEqual(u1_auth.permissions['repositories_groups'],
580 perms['repositories_groups'])
588 perms['repositories_groups'])
589
590 def test_repo_in_group_permissions(self):
591 self.g1 = _make_group('group1', skip_if_exists=True)
592 self.g2 = _make_group('group2', skip_if_exists=True)
593 Session.commit()
594 # both perms should be read !
595 u1_auth = AuthUser(user_id=self.u1.user_id)
596 self.assertEqual(u1_auth.permissions['repositories_groups'],
597 {u'group1': u'group.read', u'group2': u'group.read'})
598
599 a1_auth = AuthUser(user_id=self.anon.user_id)
600 self.assertEqual(a1_auth.permissions['repositories_groups'],
601 {u'group1': u'group.read', u'group2': u'group.read'})
602
603 #Change perms to none for both groups
604 ReposGroupModel().grant_user_permission(repos_group=self.g1,
605 user=self.anon,
606 perm='group.none')
607 ReposGroupModel().grant_user_permission(repos_group=self.g2,
608 user=self.anon,
609 perm='group.none')
610
611
612 u1_auth = AuthUser(user_id=self.u1.user_id)
613 self.assertEqual(u1_auth.permissions['repositories_groups'],
614 {u'group1': u'group.none', u'group2': u'group.none'})
615
616 a1_auth = AuthUser(user_id=self.anon.user_id)
617 self.assertEqual(a1_auth.permissions['repositories_groups'],
618 {u'group1': u'group.none', u'group2': u'group.none'})
619
620 # add repo to group
621 form_data = {
622 'repo_name':HG_REPO,
623 'repo_name_full':os.path.join(self.g1.group_name,HG_REPO),
624 'repo_type':'hg',
625 'clone_uri':'',
626 'repo_group':self.g1.group_id,
627 'description':'desc',
628 'private':False
629 }
630 self.test_repo = RepoModel().create(form_data, cur_user=self.u1)
631 Session.commit()
632
633 u1_auth = AuthUser(user_id=self.u1.user_id)
634 self.assertEqual(u1_auth.permissions['repositories_groups'],
635 {u'group1': u'group.none', u'group2': u'group.none'})
636
637 a1_auth = AuthUser(user_id=self.anon.user_id)
638 self.assertEqual(a1_auth.permissions['repositories_groups'],
639 {u'group1': u'group.none', u'group2': u'group.none'})
640
641 #grant permission for u2 !
642 ReposGroupModel().grant_user_permission(repos_group=self.g1,
643 user=self.u2,
644 perm='group.read')
645 ReposGroupModel().grant_user_permission(repos_group=self.g2,
646 user=self.u2,
647 perm='group.read')
648 Session.commit()
649 self.assertNotEqual(self.u1, self.u2)
650 #u1 and anon should have not change perms while u2 should !
651 u1_auth = AuthUser(user_id=self.u1.user_id)
652 self.assertEqual(u1_auth.permissions['repositories_groups'],
653 {u'group1': u'group.none', u'group2': u'group.none'})
654
655 u2_auth = AuthUser(user_id=self.u2.user_id)
656 self.assertEqual(u2_auth.permissions['repositories_groups'],
657 {u'group1': u'group.read', u'group2': u'group.read'})
658
659 a1_auth = AuthUser(user_id=self.anon.user_id)
660 self.assertEqual(a1_auth.permissions['repositories_groups'],
661 {u'group1': u'group.none', u'group2': u'group.none'})
662
663 def test_repo_group_user_as_user_group_member(self):
664 # create Group1
665 self.g1 = _make_group('group1', skip_if_exists=True)
666 Session.commit()
667 a1_auth = AuthUser(user_id=self.anon.user_id)
668
669 self.assertEqual(a1_auth.permissions['repositories_groups'],
670 {u'group1': u'group.read'})
671
672 # set default permission to none
673 ReposGroupModel().grant_user_permission(repos_group=self.g1,
674 user=self.anon,
675 perm='group.none')
676 # make group
677 self.ug1 = UsersGroupModel().create('G1')
678 # add user to group
679 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
680 Session.commit()
681
682 # check if user is in the group
683 membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
684 self.assertEqual(membrs, [self.u1.user_id])
685 # add some user to that group
686
687 # check his permissions
688 a1_auth = AuthUser(user_id=self.anon.user_id)
689 self.assertEqual(a1_auth.permissions['repositories_groups'],
690 {u'group1': u'group.none'})
691
692 u1_auth = AuthUser(user_id=self.u1.user_id)
693 self.assertEqual(u1_auth.permissions['repositories_groups'],
694 {u'group1': u'group.none'})
695
696 # grant ug1 read permissions for
697 ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
698 group_name=self.ug1,
699 perm='group.read')
700 Session.commit()
701 # check if the
702 obj = Session.query(UsersGroupRepoGroupToPerm)\
703 .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
704 .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
705 .scalar()
706 self.assertEqual(obj.permission.permission_name, 'group.read')
707
708 a1_auth = AuthUser(user_id=self.anon.user_id)
709
710 self.assertEqual(a1_auth.permissions['repositories_groups'],
711 {u'group1': u'group.none'})
712
713 u1_auth = AuthUser(user_id=self.u1.user_id)
714 self.assertEqual(u1_auth.permissions['repositories_groups'],
715 {u'group1': u'group.read'})
@@ -94,8 +94,9 b' setup('
94 main = pylons.util:PylonsInstaller
94 main = pylons.util:PylonsInstaller
95
95
96 [paste.global_paster_command]
96 [paste.global_paster_command]
97 make-index = rhodecode.lib.indexers:MakeIndex
97 make-index=rhodecode.lib.indexers:MakeIndex
98 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
98 make-rcext=rhodecode.config.rcextensions.make_rcextensions:MakeRcExt
99 upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
99 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
100 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
100 """,
101 """,
101 )
102 )
@@ -17,6 +17,7 b' pdebug = false'
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20
21
21 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
22 #smtp_username =
23 #smtp_username =
@@ -24,6 +25,8 b' pdebug = false'
24 #smtp_port =
25 #smtp_port =
25 #smtp_use_tls = false
26 #smtp_use_tls = false
26 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
27
30
28 [server:main]
31 [server:main]
29 ##nr of threads to spawn
32 ##nr of threads to spawn
@@ -53,6 +56,42 b' commit_parse_limit = 25'
53 use_gravatar = true
56 use_gravatar = true
54 container_auth_enabled = false
57 container_auth_enabled = false
55 proxypass_auth_enabled = false
58 proxypass_auth_enabled = false
59
60
61 ## overwrite schema of clone url
62 ## available vars:
63 ## scheme - http/https
64 ## user - current user
65 ## pass - password
66 ## netloc - network location
67 ## path - usually repo_name
68
69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
70
71 ## issue tracking mapping for commits messages
72 ## comment out issue_pat, issue_server, issue_prefix to enable
73
74 ## pattern to get the issues from commit messages
75 ## default one used here is #<numbers> with a regex passive group for `#`
76 ## {id} will be all groups matched from this pattern
77
78 issue_pat = (?:\s*#)(\d+)
79
80 ## server url to the issue, each {id} will be replaced with match
81 ## fetched from the regex and {repo} is replaced with repository name
82
83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
84
85 ## prefix to add to link to indicate it's an url
86 ## #314 will be replaced by <issue_prefix><id>
87
88 issue_prefix = #
89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
56
95
57 ####################################
96 ####################################
58 ### CELERY CONFIG ####
97 ### CELERY CONFIG ####
@@ -86,6 +125,7 b' celery.always.eager = false'
86 ####################################
125 ####################################
87 beaker.cache.data_dir=/tmp/data/cache/data
126 beaker.cache.data_dir=/tmp/data/cache/data
88 beaker.cache.lock_dir=/tmp/data/cache/lock
127 beaker.cache.lock_dir=/tmp/data/cache/lock
128
89 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
129 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90
130
91 beaker.cache.super_short_term.type=memory
131 beaker.cache.super_short_term.type=memory
@@ -118,12 +158,27 b' beaker.cache.sql_cache_long.key_length ='
118 ## Type of storage used for the session, current types are
158 ## Type of storage used for the session, current types are
119 ## dbm, file, memcached, database, and memory.
159 ## dbm, file, memcached, database, and memory.
120 ## The storage uses the Container API
160 ## The storage uses the Container API
121 ##that is also used by the cache system.
161 ## that is also used by the cache system.
122 beaker.session.type = file
162
163 ## db session example
164
165 #beaker.session.type = ext:database
166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
167 #beaker.session.table_name = db_session
168
169 ## encrypted cookie session, good for many instances
170 #beaker.session.type = cookie
123
171
172 beaker.session.type = file
124 beaker.session.key = rhodecode
173 beaker.session.key = rhodecode
125 beaker.session.secret = g654dcno0-9873jhgfreyu
174 # secure cookie requires AES python libraries
175 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
176 #beaker.session.validate_key = 9712sds2212c--zxc123
126 beaker.session.timeout = 36000
177 beaker.session.timeout = 36000
178 beaker.session.httponly = true
179
180 ## uncomment for https secure cookie
181 beaker.session.secure = false
127
182
128 ##auto save the session to not to use .save()
183 ##auto save the session to not to use .save()
129 beaker.session.auto = False
184 beaker.session.auto = False
@@ -131,7 +186,7 b' beaker.session.auto = False'
131 ##true exire at browser close
186 ##true exire at browser close
132 #beaker.session.cookie_expires = 3600
187 #beaker.session.cookie_expires = 3600
133
188
134
189
135 ################################################################################
190 ################################################################################
136 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
137 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -151,15 +206,17 b' logview.pylons.util = #eee'
151 #########################################################
206 #########################################################
152 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
207 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
153 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
208 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
154 #sqlalchemy.db1.echo = false
209 #sqlalchemy.db1.url = mysql://root:qwe123qwe@localhost/rhodecode_tests
155 #sqlalchemy.db1.pool_recycle = 3600
210
156 sqlalchemy.convert_unicode = true
211 sqlalchemy.db1.echo = false
212 sqlalchemy.db1.pool_recycle = 3600
213 sqlalchemy.db1.convert_unicode = true
157
214
158 ################################
215 ################################
159 ### LOGGING CONFIGURATION ####
216 ### LOGGING CONFIGURATION ####
160 ################################
217 ################################
161 [loggers]
218 [loggers]
162 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
219 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
163
220
164 [handlers]
221 [handlers]
165 keys = console
222 keys = console
@@ -205,6 +262,12 b' handlers = console'
205 qualname = sqlalchemy.engine
262 qualname = sqlalchemy.engine
206 propagate = 0
263 propagate = 0
207
264
265 [logger_whoosh_indexer]
266 level = DEBUG
267 handlers =
268 qualname = whoosh_indexer
269 propagate = 1
270
208 ##############
271 ##############
209 ## HANDLERS ##
272 ## HANDLERS ##
210 ##############
273 ##############
General Comments 0
You need to be logged in to leave comments. Login now