##// 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 6 *.egg
7 7
8 8 syntax: regexp
9 ^rcextensions
9 10 ^build
10 11 ^docs/build/
11 12 ^docs/_build/
@@ -15,10 +15,11 b' RhodeCode is similar in some respects to'
15 15 however RhodeCode can be run as standalone hosted application on your own server.
16 16 It is open source and donation ware and focuses more on providing a customized,
17 17 self administered interface for Mercurial_ and GIT_ repositories.
18 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
19 handle multiple different version control systems.
18 RhodeCode works on *nix systems and Windows it is powered by a vcs_ library
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 24 Installation
24 25 ------------
@@ -99,7 +100,7 b' RhodeCode Features'
99 100 - Intelligent cache with invalidation after push or project change, provides
100 101 high performance and always up to date data.
101 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 104 - Backup scripts can do backup of whole app and send it over scp to desired
104 105 location
105 106 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
@@ -93,6 +93,11 b' issue_prefix = #'
93 93 ## all running rhodecode instances. Leave empty if you don't use it
94 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 102 ### CELERY CONFIG ####
98 103 ####################################
@@ -171,6 +176,7 b' beaker.cache.sql_cache_long.key_length ='
171 176
172 177 beaker.session.type = file
173 178 beaker.session.key = rhodecode
179 # secure cookie requires AES python libraries
174 180 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
175 181 #beaker.session.validate_key = 9712sds2212c--zxc123
176 182 beaker.session.timeout = 36000
@@ -207,13 +213,13 b' logview.pylons.util = #eee'
207 213 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
208 214 sqlalchemy.db1.echo = false
209 215 sqlalchemy.db1.pool_recycle = 3600
210 sqlalchemy.convert_unicode = true
216 sqlalchemy.db1.convert_unicode = true
211 217
212 218 ################################
213 219 ### LOGGING CONFIGURATION ####
214 220 ################################
215 221 [loggers]
216 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
222 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
217 223
218 224 [handlers]
219 225 keys = console, console_sql
@@ -259,6 +265,12 b' handlers = console_sql'
259 265 qualname = sqlalchemy.engine
260 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 275 ## HANDLERS ##
264 276 ##############
@@ -27,7 +27,7 b' API ACCESS'
27 27 All clients are required to send JSON-RPC spec JSON data::
28 28
29 29 {
30 "id:<id>,
30 "id:"<id>",
31 31 "api_key":"<api_key>",
32 32 "method":"<method_name>",
33 33 "args":{"<arg_key>":"<arg_val>"}
@@ -50,9 +50,9 b' Simply provide'
50 50 RhodeCode API will return always a JSON-RPC response::
51 51
52 52 {
53 "id":<id>,
54 "result": "<result>",
55 "error": null
53 "id":<id>, # matching id sent by request
54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 "error": "null"|<error_message> # JSON formatted error (if any)
56 56 }
57 57
58 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 73 INPUT::
74 74
75 id : <id_for_response>
75 76 api_key : "<api_key>"
76 77 method : "pull"
77 78 args : {
@@ -94,6 +95,7 b' rights.'
94 95
95 96 INPUT::
96 97
98 id : <id_for_response>
97 99 api_key : "<api_key>"
98 100 method : "get_user"
99 101 args : {
@@ -111,7 +113,15 b' OUTPUT::'
111 113 "email" : "<email>",
112 114 "active" : "<bool>",
113 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 127 error: null
@@ -126,6 +136,7 b' belonging to user with admin rights.'
126 136
127 137 INPUT::
128 138
139 id : <id_for_response>
129 140 api_key : "<api_key>"
130 141 method : "get_users"
131 142 args : { }
@@ -141,7 +152,8 b' OUTPUT::'
141 152 "email" : "<email>",
142 153 "active" : "<bool>",
143 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 170 INPUT::
159 171
172 id : <id_for_response>
160 173 api_key : "<api_key>"
161 174 method : "create_user"
162 175 args : {
@@ -188,6 +201,7 b' be executed only using api_key belonging'
188 201
189 202 INPUT::
190 203
204 id : <id_for_response>
191 205 api_key : "<api_key>"
192 206 method : "update_user"
193 207 args : {
@@ -220,6 +234,7 b' belonging to user with admin rights.'
220 234
221 235 INPUT::
222 236
237 id : <id_for_response>
223 238 api_key : "<api_key>"
224 239 method : "get_users_group"
225 240 args : {
@@ -258,6 +273,7 b' api_key belonging to user with admin rig'
258 273
259 274 INPUT::
260 275
276 id : <id_for_response>
261 277 api_key : "<api_key>"
262 278 method : "get_users_groups"
263 279 args : { }
@@ -296,6 +312,7 b' belonging to user with admin rights'
296 312
297 313 INPUT::
298 314
315 id : <id_for_response>
299 316 api_key : "<api_key>"
300 317 method : "create_users_group"
301 318 args: {
@@ -322,6 +339,7 b' belonging to user with admin rights'
322 339
323 340 INPUT::
324 341
342 id : <id_for_response>
325 343 api_key : "<api_key>"
326 344 method : "add_user_users_group"
327 345 args: {
@@ -350,6 +368,7 b' using api_key belonging to user with adm'
350 368
351 369 INPUT::
352 370
371 id : <id_for_response>
353 372 api_key : "<api_key>"
354 373 method : "remove_user_from_users_group"
355 374 args: {
@@ -370,12 +389,14 b' OUTPUT::'
370 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 394 be executed only using api_key belonging to user with admin rights.
375 395
376 396
377 397 INPUT::
378 398
399 id : <id_for_response>
379 400 api_key : "<api_key>"
380 401 method : "get_repo"
381 402 args: {
@@ -391,7 +412,9 b' OUTPUT::'
391 412 "type" : "<type>",
392 413 "description" : "<description>",
393 414 "members" : [
394 { "id" : "<userid>",
415 {
416 "type": "user",
417 "id" : "<userid>",
395 418 "username" : "<username>",
396 419 "firstname": "<firstname>",
397 420 "lastname" : "<lastname>",
@@ -403,6 +426,7 b' OUTPUT::'
403 426 },
404 427 …
405 428 {
429 "type": "users_group",
406 430 "id" : "<usersgroupid>",
407 431 "name" : "<usersgroupname>",
408 432 "active": "<bool>",
@@ -423,6 +447,7 b' belonging to user with admin rights'
423 447
424 448 INPUT::
425 449
450 id : <id_for_response>
426 451 api_key : "<api_key>"
427 452 method : "get_repos"
428 453 args: { }
@@ -452,6 +477,7 b' with admin rights'
452 477
453 478 INPUT::
454 479
480 id : <id_for_response>
455 481 api_key : "<api_key>"
456 482 method : "get_repo_nodes"
457 483 args: {
@@ -485,6 +511,7 b' and create "baz" repository with "bar" a'
485 511
486 512 INPUT::
487 513
514 id : <id_for_response>
488 515 api_key : "<api_key>"
489 516 method : "create_repo"
490 517 args: {
@@ -514,6 +541,7 b' belonging to user with admin rights.'
514 541
515 542 INPUT::
516 543
544 id : <id_for_response>
517 545 api_key : "<api_key>"
518 546 method : "delete_repo"
519 547 args: {
@@ -538,6 +566,7 b' with admin rights.'
538 566
539 567 INPUT::
540 568
569 id : <id_for_response>
541 570 api_key : "<api_key>"
542 571 method : "grant_user_permission"
543 572 args: {
@@ -563,6 +592,7 b' only using api_key belonging to user wit'
563 592
564 593 INPUT::
565 594
595 id : <id_for_response>
566 596 api_key : "<api_key>"
567 597 method : "revoke_user_permission"
568 598 args: {
@@ -588,6 +618,7 b' api_key belonging to user with admin rig'
588 618
589 619 INPUT::
590 620
621 id : <id_for_response>
591 622 api_key : "<api_key>"
592 623 method : "grant_users_group_permission"
593 624 args: {
@@ -612,6 +643,7 b' executed only using api_key belonging to'
612 643
613 644 INPUT::
614 645
646 id : <id_for_response>
615 647 api_key : "<api_key>"
616 648 method : "revoke_users_group_permission"
617 649 args: {
@@ -5,6 +5,44 b' Changelog'
5 5 =========
6 6
7 7
8 1.3.4 (**2012-03-28**)
9 ----------------------
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
8 46
9 47 1.3.3 (**2012-03-02**)
10 48 ----------------------
@@ -23,6 +23,7 b' Users Guide'
23 23 usage/git_support
24 24 usage/statistics
25 25 usage/backup
26 usage/debugging
26 27
27 28 **Develop**
28 29
@@ -20,9 +20,10 b' following command to do this::'
20 20
21 21
22 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 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 27 the following command::
27 28
28 29 paster setup-app production.ini
@@ -57,15 +58,18 b' You are now ready to use RhodeCode, to r'
57 58 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
58 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 using, then from within the RhodeCode web application choose Admin >
62 repositories. Then choose Add New Repository. Add the repository you copied
63 into the root. Test that you can browse your repository from within RhodeCode
64 and then try cloning your repository from RhodeCode with::
61 Optionally users can create `rcextensions` package that extends RhodeCode
62 functionality. To do this simply execute::
63
64 paster make-rcext production.ini
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 74 Using RhodeCode with SSH
71 75 ------------------------
@@ -47,6 +47,9 b' This will upgrade the schema and update '
47 47 and will always recheck the settings of the application, if there are no new
48 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 54 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
52 55 .. _python: http://www.python.org/
@@ -71,6 +71,11 b' RhodeCode will send mails on user regist'
71 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 79 Trending source files
75 80 ---------------------
76 81
@@ -93,6 +93,11 b' issue_prefix = #'
93 93 ## all running rhodecode instances. Leave empty if you don't use it
94 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 102 ### CELERY CONFIG ####
98 103 ####################################
@@ -208,13 +213,13 b' logview.pylons.util = #eee'
208 213 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
209 214 sqlalchemy.db1.echo = false
210 215 sqlalchemy.db1.pool_recycle = 3600
211 sqlalchemy.convert_unicode = true
216 sqlalchemy.db1.convert_unicode = true
212 217
213 218 ################################
214 219 ### LOGGING CONFIGURATION ####
215 220 ################################
216 221 [loggers]
217 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
222 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
218 223
219 224 [handlers]
220 225 keys = console, console_sql
@@ -260,6 +265,12 b' handlers = console_sql'
260 265 qualname = sqlalchemy.engine
261 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 275 ## HANDLERS ##
265 276 ##############
@@ -1,17 +1,17 b''
1 1 Pylons==1.0.0
2 2 Beaker==1.6.3
3 WebHelpers>=1.2
3 WebHelpers==1.3
4 4 formencode==1.2.4
5 SQLAlchemy==0.7.4
6 Mako==0.5.0
5 SQLAlchemy==0.7.6
6 Mako==0.6.2
7 7 pygments>=1.4
8 8 whoosh>=2.3.0,<2.4
9 9 celery>=2.2.5,<2.3
10 10 babel
11 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 13 webob==1.0.8
14 14 markdown==2.1.1
15 15 docutils==0.8.1
16 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 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 9 :created_on: Apr 9, 2010
10 10 :author: marcink
@@ -26,8 +26,18 b''
26 26 import sys
27 27 import platform
28 28
29 VERSION = (1, 3, 3)
30 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 VERSION = (1, 3, 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 41 __dbversion__ = 5 # defines current db version for migrations
32 42 __platform__ = platform.system()
33 43 __license__ = 'GPLv3'
@@ -39,16 +49,16 b" PLATFORM_OTHERS = ('Linux', 'Darwin', 'F"
39 49 requirements = [
40 50 "Pylons==1.0.0",
41 51 "Beaker==1.6.3",
42 "WebHelpers>=1.2",
52 "WebHelpers==1.3",
43 53 "formencode==1.2.4",
44 "SQLAlchemy==0.7.4",
45 "Mako==0.5.0",
54 "SQLAlchemy==0.7.6",
55 "Mako==0.6.2",
46 56 "pygments>=1.4",
47 57 "whoosh>=2.3.0,<2.4",
48 58 "celery>=2.2.5,<2.3",
49 59 "babel",
50 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 62 "webob==1.0.8",
53 63 "markdown==2.1.1",
54 64 "docutils==0.8.1",
@@ -65,17 +75,6 b' else:'
65 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 78 def get_version():
80 79 """Returns shorter version (digit parts only) as string."""
81 80
@@ -90,3 +89,6 b' CELERY_ON = False'
90 89
91 90 # link to config for pylons
92 91 CONFIG = {}
92
93 # Linked module for extensions
94 EXTENSIONS = {}
@@ -93,6 +93,11 b' issue_prefix = #'
93 93 ## all running rhodecode instances. Leave empty if you don't use it
94 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 102 ### CELERY CONFIG ####
98 103 ####################################
@@ -218,13 +223,13 b' sqlalchemy.db1.url = sqlite:///%(here)s/'
218 223
219 224 sqlalchemy.db1.echo = false
220 225 sqlalchemy.db1.pool_recycle = 3600
221 sqlalchemy.convert_unicode = true
226 sqlalchemy.db1.convert_unicode = true
222 227
223 228 ################################
224 229 ### LOGGING CONFIGURATION ####
225 230 ################################
226 231 [loggers]
227 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
232 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
228 233
229 234 [handlers]
230 235 keys = console, console_sql
@@ -270,6 +275,12 b' handlers = console_sql'
270 275 qualname = sqlalchemy.engine
271 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 285 ## HANDLERS ##
275 286 ##############
@@ -2,21 +2,24 b''
2 2
3 3 import os
4 4 import logging
5 import rhodecode
5 6
6 7 from mako.lookup import TemplateLookup
7 8 from pylons.configuration import PylonsConfig
8 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 14 import rhodecode.lib.app_globals as app_globals
12 import rhodecode.lib.helpers
13 15
14 16 from rhodecode.config.routing import make_map
15 # don't remove this import it does magic for celery
16 from rhodecode.lib import celerypylons, str2bool
17 from rhodecode.lib import engine_from_config
17
18 from rhodecode.lib import helpers
18 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 23 from rhodecode.model import init_model
21 24 from rhodecode.model.scm import ScmModel
22 25
@@ -24,17 +27,20 b' log = logging.getLogger(__name__)'
24 27
25 28
26 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 32 object
29 33 """
30 34 config = PylonsConfig()
31 35
32 36 # Pylons paths
33 37 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
34 paths = dict(root=root,
38 paths = dict(
39 root=root,
35 40 controllers=os.path.join(root, 'controllers'),
36 41 static_files=os.path.join(root, 'public'),
37 templates=[os.path.join(root, 'templates')])
42 templates=[os.path.join(root, 'templates')]
43 )
38 44
39 45 # Initialize config with the basic options
40 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 51 config['routes.map'] = make_map(config)
46 52 config['pylons.app_globals'] = app_globals.Globals(config)
47 config['pylons.h'] = rhodecode.lib.helpers
53 config['pylons.h'] = helpers
48 54 rhodecode.CONFIG = config
55
56 load_rcextensions(root_path=config['here'])
57
49 58 # Setup cache object as early as possible
50 59 import pylons
51 60 pylons.cache._push_object(config['pylons.app_globals'].cache)
@@ -284,7 +284,6 b' class ReposController(BaseController):'
284 284
285 285 :param repo_name:
286 286 """
287
288 287 try:
289 288 RepoModel().revoke_user_permission(repo=repo_name,
290 289 user=request.POST['user_id'])
@@ -145,11 +145,12 b' class UsersController(BaseController):'
145 145 user_model = UserModel()
146 146 try:
147 147 user_model.delete(id)
148 Session.commit()
148 149 h.flash(_('successfully deleted user'), category='success')
149 Session.commit()
150 150 except (UserOwnsReposException, DefaultUserException), e:
151 h.flash(str(e), category='warning')
151 h.flash(e, category='warning')
152 152 except Exception:
153 log.error(traceback.format_exc())
153 154 h.flash(_('An error occurred during deletion of user'),
154 155 category='error')
155 156 return redirect(url('users'))
@@ -32,8 +32,9 b' from pylons import request, session, tmp'
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 from rhodecode.lib import helpers as h
35 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 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 39 from rhodecode.lib.base import BaseController, render
39 40
@@ -233,10 +233,10 b' class JSONRPCController(WSGIController):'
233 233 try:
234 234 return json.dumps(response)
235 235 except TypeError, e:
236 log.debug('Error encoding response: %s' % e)
236 log.error('API FAILED. Error encoding response: %s' % e)
237 237 return json.dumps(
238 238 dict(
239 self._req_id,
239 id=self._req_id,
240 240 result=None,
241 241 error="Error encoding response"
242 242 )
@@ -30,16 +30,15 b' import logging'
30 30
31 31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator, PasswordGenerator
33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
34 34
35 35 from rhodecode.model.meta import Session
36 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 38 from rhodecode.model.repo import RepoModel
39 39 from rhodecode.model.user import UserModel
40 40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.repos_group import ReposGroupModel
42
41 from rhodecode.lib.utils import map_groups
43 42
44 43 log = logging.getLogger(__name__)
45 44
@@ -100,7 +99,9 b' class ApiController(JSONRPCController):'
100 99 email=user.email,
101 100 active=user.active,
102 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 107 @HasPermissionAllDecorator('hg.admin')
@@ -122,7 +123,8 b' class ApiController(JSONRPCController):'
122 123 email=user.email,
123 124 active=user.active,
124 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 130 return result
@@ -282,7 +284,7 b' class ApiController(JSONRPCController):'
282 284 @HasPermissionAllDecorator('hg.admin')
283 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 289 :param apiuser:
288 290 :param group_name:
@@ -360,7 +362,7 b' class ApiController(JSONRPCController):'
360 362 user = user.user
361 363 members.append(
362 364 dict(
363 type_="user",
365 type="user",
364 366 id=user.user_id,
365 367 username=user.username,
366 368 firstname=user.name,
@@ -377,7 +379,7 b' class ApiController(JSONRPCController):'
377 379 users_group = users_group.users_group
378 380 members.append(
379 381 dict(
380 type_="users_group",
382 type="users_group",
381 383 id=users_group.users_group_id,
382 384 name=users_group.users_group_name,
383 385 active=users_group.users_group_active,
@@ -464,15 +466,10 b' class ApiController(JSONRPCController):'
464 466 if Repository.get_by_repo_name(repo_name):
465 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 470 real_name = groups[-1]
469 groups = groups[:-1]
470 parent_id = None
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
471 # create structure of groups
472 group = map_groups(repo_name)
476 473
477 474 repo = RepoModel().create(
478 475 dict(
@@ -481,7 +478,7 b' class ApiController(JSONRPCController):'
481 478 description=description,
482 479 private=private,
483 480 repo_type=repo_type,
484 repo_group=parent_id,
481 repo_group=group.group_id if group else None,
485 482 clone_uri=clone_uri
486 483 ),
487 484 owner
@@ -31,7 +31,7 b' import binascii'
31 31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 32 from rhodecode.lib.base import BaseRepoController, render
33 33 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib import safe_unicode
34 from rhodecode.lib.utils2 import safe_unicode
35 35 log = logging.getLogger(__name__)
36 36
37 37
@@ -51,13 +51,18 b' from rhodecode.lib.diffs import wrapped_'
51 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 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 64 def get_ignore_ws(fid, GET):
60 ig_ws_global = request.GET.get('ignorews')
65 ig_ws_global = GET.get('ignorews')
61 66 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 67 if ig_ws:
63 68 try:
@@ -67,12 +72,13 b' def get_ignore_ws(fid, GET):'
67 72 return ig_ws_global
68 73
69 74
70 def _ignorews_url(fileid=None):
71
75 def _ignorews_url(GET, fileid=None):
76 fileid = str(fileid) if fileid else None
72 77 params = defaultdict(list)
78 _update_with_GET(params, GET)
73 79 lbl = _('show white space')
74 ig_ws = get_ignore_ws(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
80 ig_ws = get_ignore_ws(fileid, GET)
81 ln_ctx = get_line_ctx(fileid, GET)
76 82 # global option
77 83 if fileid is None:
78 84 if ig_ws is None:
@@ -98,7 +104,7 b' def _ignorews_url(fileid=None):'
98 104
99 105
100 106 def get_line_ctx(fid, GET):
101 ln_ctx_global = request.GET.get('context')
107 ln_ctx_global = GET.get('context')
102 108 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103 109
104 110 if ln_ctx:
@@ -112,16 +118,19 b' def get_line_ctx(fid, GET):'
112 118 return
113 119
114 120
115 def _context_url(fileid=None):
121 def _context_url(GET, fileid=None):
116 122 """
117 123 Generates url for context lines
118 124
119 125 :param fileid:
120 126 """
121 ig_ws = get_ignore_ws(fileid, request.GET)
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
127
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 132 params = defaultdict(list)
133 _update_with_GET(params, GET)
125 134
126 135 # global option
127 136 if fileid is None:
@@ -162,7 +171,7 b' class ChangesetController(BaseRepoContro'
162 171 c.anchor_url = anchor_url
163 172 c.ignorews_url = _ignorews_url
164 173 c.context_url = _context_url
165
174 limit_off = request.GET.get('fulldiff')
166 175 #get ranges of revisions if preset
167 176 rev_range = revision.split('...')[:2]
168 177 enable_comments = True
@@ -220,7 +229,7 b' class ChangesetController(BaseRepoContro'
220 229 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 230 lim = self.cut_off_limit
222 231 if cumulative_diff > self.cut_off_limit:
223 lim = -1
232 lim = -1 if limit_off is None else None
224 233 size, cs1, cs2, diff, st = wrapped_diff(
225 234 filenode_old=None,
226 235 filenode_new=node,
@@ -251,7 +260,7 b' class ChangesetController(BaseRepoContro'
251 260 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
252 261 lim = self.cut_off_limit
253 262 if cumulative_diff > self.cut_off_limit:
254 lim = -1
263 lim = -1 if limit_off is None else None
255 264 size, cs1, cs2, diff, st = wrapped_diff(
256 265 filenode_old=filenode_old,
257 266 filenode_new=node,
@@ -28,7 +28,7 b' import logging'
28 28 from pylons import url, response, tmpl_context as c
29 29 from pylons.i18n.translation import _
30 30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.lib.utils2 import safe_unicode
32 32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 33 from rhodecode.lib.base import BaseRepoController
34 34
@@ -32,24 +32,26 b' from pylons.i18n.translation import _'
32 32 from pylons.controllers.util import redirect
33 33 from pylons.decorators import jsonify
34 34
35 from rhodecode.lib.vcs.conf import settings
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
38 NodeAlreadyExistsError
39 from rhodecode.lib.vcs.nodes import FileNode
35 from rhodecode.lib import diffs
36 from rhodecode.lib import helpers as h
40 37
41 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 40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 41 from rhodecode.lib.base import BaseRepoController, render
45 42 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib import diffs
47 import rhodecode.lib.helpers as h
43 from rhodecode.lib.vcs.conf import settings
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 49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.scm import ScmModel
51
49 52 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
50 53 _context_url, get_line_ctx, get_ignore_ws
51 from rhodecode.lib.diffs import wrapped_diff
52 from rhodecode.model.scm import ScmModel
54
53 55
54 56 log = logging.getLogger(__name__)
55 57
@@ -447,7 +449,7 b' class FilesController(BaseRepoController'
447 449 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
448 450
449 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 453 filenode_new=node2,
452 454 cut_off_limit=lim,
453 455 ignore_whitespace=ign_whitespace_lcl,
@@ -44,7 +44,7 b' class HomeController(BaseController):'
44 44 def index(self):
45 45 c.repos_list = self.scm_model.get_repos()
46 46 c.groups = self.scm_model.get_repos_groups()
47
47 c.group = None
48 48 return render('/index.html')
49 49
50 50 def repo_switcher(self):
@@ -26,6 +26,7 b''
26 26 import traceback
27 27 import calendar
28 28 import logging
29 import urllib
29 30 from time import mktime
30 31 from datetime import timedelta, date
31 32 from urlparse import urlparse
@@ -39,15 +40,15 b' from pylons.i18n.translation import _'
39 40
40 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 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 46 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 47 from rhodecode.lib.base import BaseRepoController, render
46 48 from rhodecode.lib.utils import EmptyChangeset
47 49 from rhodecode.lib.markup_renderer import MarkupRenderer
48 50 from rhodecode.lib.celerylib import run_task
49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
50 LANGUAGES_EXTENSIONS_MAP
51 from rhodecode.lib.celerylib.tasks import get_commits_stats
51 52 from rhodecode.lib.helpers import RepoPage
52 53 from rhodecode.lib.compat import json, OrderedDict
53 54
@@ -91,34 +92,37 b' class SummaryController(BaseRepoControll'
91 92
92 93 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 94 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94
95 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
95 96 uri_dict = {
96 97 'user': username,
97 98 'pass': password,
98 99 'scheme': parsed_url.scheme,
99 100 'netloc': parsed_url.netloc,
100 'path': parsed_url.path
101 'path': decoded_path
101 102 }
103
102 104 uri = uri_tmpl % uri_dict
103 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 109 uri_id = uri_tmpl % uri_dict
106 110
107 111 c.clone_repo_url = uri
108 112 c.clone_repo_url_id = uri_id
109 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 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 117 except ChangesetError:
114 c.repo_tags[name] = EmptyChangeset(hash)
118 c.repo_tags[name] = EmptyChangeset(hash_)
115 119
116 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 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 124 except ChangesetError:
121 c.repo_branches[name] = EmptyChangeset(hash)
125 c.repo_branches[name] = EmptyChangeset(hash_)
122 126
123 127 td = date.today() + timedelta(days=1)
124 128 td_1m = td - timedelta(days=calendar.mdays[td.month])
@@ -175,7 +179,7 b' class SummaryController(BaseRepoControll'
175 179 if c.enable_downloads:
176 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 183 return render('summary/summary.html')
180 184
181 185 def __get_readme_data(self, repo):
@@ -206,7 +210,7 b' class SummaryController(BaseRepoControll'
206 210
207 211 return readme_data, readme_file
208 212
209 key = repo.name + '_README'
213 key = repo.repo_name + '_README'
210 214 inv = CacheInvalidation.invalidate(key)
211 215 if inv is not None:
212 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 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 4 def get_current_revision(quiet=False):
@@ -450,16 +22,3 b' def get_current_revision(quiet=False):'
450 22 print ("Cannot retrieve rhodecode's revision. Original error "
451 23 "was: %s" % err)
452 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 43 if __platform__ in PLATFORM_OTHERS:
44 44 import bcrypt
45 45
46 from rhodecode.lib import str2bool, safe_unicode
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
47 47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 49 from rhodecode.lib.auth_ldap import AuthLdap
@@ -521,8 +521,7 b' class PermsDecorator(object):'
521 521 self.user = cls.rhodecode_user
522 522 self.user_perms = self.user.permissions
523 523 log.debug('checking %s permissions %s for %s %s',
524 self.__class__.__name__, self.required_perms, cls,
525 self.user)
524 self.__class__.__name__, self.required_perms, cls, self.user)
526 525
527 526 if self.check_permissions():
528 527 log.debug('Permission granted for %s %s' % (cls, self.user))
@@ -604,6 +603,7 b' class HasRepoPermissionAnyDecorator(Perm'
604 603 user_perms = set([self.user_perms['repositories'][repo_name]])
605 604 except KeyError:
606 605 return False
606
607 607 if self.required_perms.intersection(user_perms):
608 608 return True
609 609 return False
@@ -655,29 +655,37 b' class PermsFunction(object):'
655 655
656 656 for perm in perms:
657 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 659 self.required_perms = set(perms)
660 660 self.user_perms = None
661 self.granted_for = ''
662 661 self.repo_name = None
662 self.group_name = None
663 663
664 664 def __call__(self, check_Location=''):
665 665 user = request.user
666 log.debug('checking %s %s %s', self.__class__.__name__,
667 self.required_perms, user)
666 cls_name = self.__class__.__name__
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 678 if not user:
669 679 log.debug('Empty request user')
670 680 return False
671 681 self.user_perms = user.permissions
672 self.granted_for = user
673
674 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 684 check_Location or 'unspecified location')
677 685 return True
678 686
679 687 else:
680 log.debug('Permission denied for %s @ %s', self.granted_for,
688 log.debug('Permission denied for user: %s @ %s', user,
681 689 check_Location or 'unspecified location')
682 690 return False
683 691
@@ -701,7 +709,6 b' class HasPermissionAny(PermsFunction):'
701 709
702 710
703 711 class HasRepoPermissionAll(PermsFunction):
704
705 712 def __call__(self, repo_name=None, check_Location=''):
706 713 self.repo_name = repo_name
707 714 return super(HasRepoPermissionAll, self).__call__(check_Location)
@@ -711,19 +718,17 b' class HasRepoPermissionAll(PermsFunction'
711 718 self.repo_name = get_repo_slug(request)
712 719
713 720 try:
714 self.user_perms = set(
721 self._user_perms = set(
715 722 [self.user_perms['repositories'][self.repo_name]]
716 723 )
717 724 except KeyError:
718 725 return False
719 self.granted_for = self.repo_name
720 if self.required_perms.issubset(self.user_perms):
726 if self.required_perms.issubset(self._user_perms):
721 727 return True
722 728 return False
723 729
724 730
725 731 class HasRepoPermissionAny(PermsFunction):
726
727 732 def __call__(self, repo_name=None, check_Location=''):
728 733 self.repo_name = repo_name
729 734 return super(HasRepoPermissionAny, self).__call__(check_Location)
@@ -733,13 +738,12 b' class HasRepoPermissionAny(PermsFunction'
733 738 self.repo_name = get_repo_slug(request)
734 739
735 740 try:
736 self.user_perms = set(
741 self._user_perms = set(
737 742 [self.user_perms['repositories'][self.repo_name]]
738 743 )
739 744 except KeyError:
740 745 return False
741 self.granted_for = self.repo_name
742 if self.required_perms.intersection(self.user_perms):
746 if self.required_perms.intersection(self._user_perms):
743 747 return True
744 748 return False
745 749
@@ -751,13 +755,12 b' class HasReposGroupPermissionAny(PermsFu'
751 755
752 756 def check_permissions(self):
753 757 try:
754 self.user_perms = set(
758 self._user_perms = set(
755 759 [self.user_perms['repositories_groups'][self.group_name]]
756 760 )
757 761 except KeyError:
758 762 return False
759 self.granted_for = self.repo_name
760 if self.required_perms.intersection(self.user_perms):
763 if self.required_perms.intersection(self._user_perms):
761 764 return True
762 765 return False
763 766
@@ -769,13 +772,12 b' class HasReposGroupPermissionAll(PermsFu'
769 772
770 773 def check_permissions(self):
771 774 try:
772 self.user_perms = set(
775 self._user_perms = set(
773 776 [self.user_perms['repositories_groups'][self.group_name]]
774 777 )
775 778 except KeyError:
776 779 return False
777 self.granted_for = self.repo_name
778 if self.required_perms.issubset(self.user_perms):
780 if self.required_perms.issubset(self._user_perms):
779 781 return True
780 782 return False
781 783
@@ -788,12 +790,16 b' class HasPermissionAnyMiddleware(object)'
788 790 self.required_perms = set(perms)
789 791
790 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 796 usr = AuthUser(user.user_id)
792 797 try:
793 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 802 self.user_perms = set()
796 self.granted_for = ''
797 803 self.username = user.username
798 804 self.repo_name = repo_name
799 805 return self.check_permissions()
@@ -803,7 +809,13 b' class HasPermissionAnyMiddleware(object)'
803 809 'permissions %s for user:%s repository:%s', self.user_perms,
804 810 self.username, self.repo_name)
805 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 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 821 return False
@@ -7,6 +7,8 b' import time'
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE
10 12
11 13 from pylons import config, tmpl_context as c, request, session, url
12 14 from pylons.controllers import WSGIController
@@ -15,7 +17,7 b' from pylons.templating import render_mak'
15 17
16 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 21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 22 HasPermissionAnyMiddleware, CookieStoreWrapper
21 23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
@@ -28,6 +30,22 b' from rhodecode.model.scm import ScmModel'
28 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 49 class BaseVCSController(object):
32 50
33 51 def __init__(self, application, config):
@@ -36,7 +54,8 b' class BaseVCSController(object):'
36 54 # base path of repo locations
37 55 self.basepath = self.config['base_path']
38 56 #authenticate this mercurial request using authfunc
39 self.authenticate = AuthBasicAuthenticator('', authfunc)
57 self.authenticate = BasicAuth('', authfunc,
58 config.get('auth_ret_code'))
40 59 self.ipaddr = '0.0.0.0'
41 60
42 61 def _handle_request(self, environ, start_response):
@@ -24,7 +24,7 b' from beaker.exceptions import BeakerExce'
24 24 from sqlalchemy.orm.interfaces import MapperOption
25 25 from sqlalchemy.orm.query import Query
26 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 30 class CachingQuery(Query):
@@ -36,7 +36,7 b' from decorator import decorator'
36 36
37 37 from rhodecode.lib.vcs.utils.lazy import LazyProperty
38 38 from rhodecode import CELERY_ON
39 from rhodecode.lib import str2bool, safe_str
39 from rhodecode.lib.utils2 import str2bool, safe_str
40 40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 41 from rhodecode.model import init_model
42 42 from rhodecode.model import meta
@@ -40,7 +40,7 b' from pylons.i18n.translation import _'
40 40 from rhodecode.lib.vcs import get_backend
41 41
42 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 44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 46 from rhodecode.lib.helpers import person
@@ -147,6 +147,7 b' def get_commits_stats(repo_name, ts_min_'
147 147 last_rev, last_rev + parse_limit)
148 148 )
149 149 for cs in repo[last_rev:last_rev + parse_limit]:
150 log.debug('parsing %s' % cs)
150 151 last_cs = cs # remember last parsed changeset
151 152 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 153 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
@@ -327,7 +328,7 b' def send_email(recipients, subject, body'
327 328 DBS = get_session()
328 329
329 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 332 if not recipients:
332 333 # if recipients are not defined we send to email_config + all admins
333 334 admins = [u.email for u in User.query()
@@ -395,6 +396,7 b' def create_repo_fork(form_data, cur_user'
395 396 DBS.commit()
396 397
397 398 def __get_codes_stats(repo_name):
399 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
398 400 repo = Repository.get_by_repo_name(repo_name).scm_instance
399 401
400 402 tip = repo.get_changeset()
@@ -1,9 +1,9 b''
1 1 import rhodecode
2 from rhodecode.lib.utils import BasePasterCommand, Command
2 from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions
3 3 from celery.app import app_or_default
4 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 8 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
9 9 'CAMQPAdminCommand', 'CeleryEventCommand']
@@ -39,9 +39,11 b' class CeleryCommand(BasePasterCommand):'
39 39 raise Exception('Please enable celery_on in .ini config '
40 40 'file before running celeryd')
41 41 rhodecode.CELERY_ON = CELERY_ON
42 load_rcextensions(config['here'])
42 43 cmd = self.celery_command(app_or_default())
43 44 return cmd.run(**vars(self.options))
44 45
46
45 47 class CeleryDaemonCommand(CeleryCommand):
46 48 """Start the celery worker
47 49
@@ -82,6 +84,7 b' class CAMQPAdminCommand(CeleryCommand):'
82 84 parser = Command.standard_parser(quiet=True)
83 85 celery_command = camqadm.AMQPAdminCommand
84 86
87
85 88 class CeleryEventCommand(CeleryCommand):
86 89 """Celery event command.
87 90
@@ -25,16 +25,93 b''
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 import datetime
29 import functools
30 import decimal
28 31 from rhodecode import __platform__, PLATFORM_WIN
29 32
30 33 #==============================================================================
31 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 91 try:
34 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 104 except ImportError:
36 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 117 # izip_longest
@@ -44,7 +121,7 b' try:'
44 121 except ImportError:
45 122 import itertools
46 123
47 def izip_longest(*args, **kwds): # noqa
124 def izip_longest(*args, **kwds):
48 125 fillvalue = kwds.get("fillvalue")
49 126
50 127 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
@@ -376,7 +376,7 b' class DbManage(object):'
376 376
377 377 if not self.tests and not test_repo_path:
378 378 path = raw_input(
379 'Enter a valid path to store repositories. '
379 'Enter a valid absolute path to store repositories. '
380 380 'All repositories in that path will be added automatically:'
381 381 )
382 382 else:
@@ -388,8 +388,12 b' class DbManage(object):'
388 388 path_ok = False
389 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 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 397 path_ok = False
394 398 log.error('No write permission to given path %s' % path)
395 399
@@ -71,9 +71,6 b' class InvalidScriptError(ScriptError):'
71 71 """Invalid script error."""
72 72
73 73
74 class InvalidVersionError(Error):
75 """Invalid version error."""
76
77 74 # migrate.changeset
78 75
79 76 class NotSupportedError(Error):
@@ -39,7 +39,7 b' from rhodecode.lib.vcs.utils.helpers imp'
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 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 43 generate_api_key, safe_unicode
44 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 45 from rhodecode.lib.compat import json
@@ -717,7 +717,7 b' class Repository(Base, BaseModel):'
717 717 return repo
718 718
719 719
720 class RepoGroup(Base, BaseModel):
720 class Group(Base, BaseModel):
721 721 __tablename__ = 'groups'
722 722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
@@ -728,8 +728,7 b' class RepoGroup(Base, BaseModel):'
728 728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 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)
732
731 parent_group = relationship('Group', remote_side=group_id)
733 732
734 733 def __init__(self, group_name='', parent_group=None):
735 734 self.group_name = group_name
@@ -39,12 +39,20 b' from webhelpers.html.tags import _set_in'
39 39
40 40 from rhodecode.lib.annotate import annotate_highlight
41 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 44 from rhodecode.lib.markup_renderer import MarkupRenderer
44 45
45 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 56 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 57 """
50 58 Reset button
@@ -67,7 +75,7 b' def FID(raw_id, path):'
67 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 81 def get_token():
@@ -86,6 +94,7 b' def get_token():'
86 94 session.save()
87 95 return session[token_key]
88 96
97
89 98 class _GetError(object):
90 99 """Get error from form_errors, and represent it as span wrapped error
91 100 message
@@ -101,6 +110,7 b' class _GetError(object):'
101 110
102 111 get_error = _GetError()
103 112
113
104 114 class _ToolTip(object):
105 115
106 116 def __call__(self, tooltip_title, trim_at=50):
@@ -112,6 +122,7 b' class _ToolTip(object):'
112 122 return escape(tooltip_title)
113 123 tooltip = _ToolTip()
114 124
125
115 126 class _FilesBreadCrumbs(object):
116 127
117 128 def __call__(self, repo_name, rev, paths):
@@ -136,8 +147,10 b' class _FilesBreadCrumbs(object):'
136 147
137 148 files_breadcrumbs = _FilesBreadCrumbs()
138 149
150
139 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 156 def wrap(self, source, outfile):
@@ -319,7 +332,7 b' flash = _Flash()'
319 332 # SCM FILTERS available via h.
320 333 #==============================================================================
321 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 336 from rhodecode.model.db import User
324 337
325 338 age = lambda x: _age(x)
@@ -759,10 +772,10 b' def fancy_file_stats(stats):'
759 772 d_v = d if d > 0 else ''
760 773
761 774 def cgen(l_type):
762 mapping = {'tr': 'top-right-rounded-corner',
763 'tl': 'top-left-rounded-corner',
764 'br': 'bottom-right-rounded-corner',
765 'bl': 'bottom-left-rounded-corner'}
775 mapping = {'tr': 'top-right-rounded-corner-mid',
776 'tl': 'top-left-rounded-corner-mid',
777 'br': 'bottom-right-rounded-corner-mid',
778 'bl': 'bottom-left-rounded-corner-mid'}
766 779 map_getter = lambda x: mapping[x]
767 780
768 781 if l_type == 'a' and d_v:
@@ -801,6 +814,12 b' def urlify_text(text_):'
801 814
802 815
803 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 823 import re
805 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 858 import re
840 859 import traceback
841 860
842 # urlify changesets
843 text_ = urlify_changesets(text_, repository)
861 def escaper(string):
862 return string.replace('<', '&lt;').replace('>', '&gt;')
844 863
845 864 def linkify_others(t, l):
846 865 urls = re.compile(r'(\<a.*?\<\/a\>)',)
@@ -852,6 +871,11 b' def urlify_commit(text_, repository=None'
852 871 links.append(e)
853 872
854 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 879 try:
856 880 conf = config['app_conf']
857 881
@@ -27,9 +27,10 b' import sys'
27 27
28 28 from mercurial.scmutil import revrange
29 29 from mercurial.node import nullrev
30
30 from rhodecode import EXTENSIONS
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.utils import action_logger
33 from inspect import isfunction
33 34
34 35
35 36 def repo_size(ui, repo, hooktype=None, **kwargs):
@@ -78,14 +79,19 b' def log_pull_action(ui, repo, **kwargs):'
78 79 :param repo:
79 80 """
80 81
81 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
82 username = extra_params['username']
83 repository = extra_params['repository']
82 extras = dict(repo.ui.configitems('rhodecode_extras'))
83 username = extras['username']
84 repository = extras['repository']
84 85 action = 'pull'
85 86
86 action_logger(username, action, repository, extra_params['ip'],
87 commit=True)
87 action_logger(username, action, repository, extras['ip'], 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 95 return 0
90 96
91 97
@@ -97,10 +103,10 b' def log_push_action(ui, repo, **kwargs):'
97 103 :param repo:
98 104 """
99 105
100 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
101 username = extra_params['username']
102 repository = extra_params['repository']
103 action = extra_params['action'] + ':%s'
106 extras = dict(repo.ui.configitems('rhodecode_extras'))
107 username = extras['username']
108 repository = extras['repository']
109 action = extras['action'] + ':%s'
104 110 node = kwargs['node']
105 111
106 112 def get_revs(repo, rev_opt):
@@ -119,16 +125,22 b' def log_push_action(ui, repo, **kwargs):'
119 125
120 126 action = action % ','.join(revs)
121 127
122 action_logger(username, action, repository, extra_params['ip'],
123 commit=True)
128 action_logger(username, action, repository, extras['ip'], 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 136 return 0
126 137
127 138
128 139 def log_create_repository(repository_dict, created_by, **kwargs):
129 140 """
130 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 145 :param repository: dict dump of repository object
134 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 174 return 0
@@ -25,6 +25,7 b''
25 25 import os
26 26 import sys
27 27 import traceback
28 import logging
28 29 from os.path import dirname as dn, join as jn
29 30
30 31 #to get the rhodecode import
@@ -46,11 +47,9 b' from rhodecode.model import init_model'
46 47 from rhodecode.model.scm import ScmModel
47 48 from rhodecode.model.repo import RepoModel
48 49 from rhodecode.config.environment import load_environment
49 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty
50 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
51
52 # EXTENSIONS WE WANT TO INDEX CONTENT OFF
53 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
50 from rhodecode.lib.utils2 import LazyProperty
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
52 load_rcextensions
54 53
55 54 # CUSTOM ANALYZER wordsplit + lowercase filter
56 55 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
@@ -84,18 +83,17 b' class MakeIndex(BasePasterCommand):'
84 83 parser = Command.standard_parser(verbose=True)
85 84
86 85 def command(self):
87
86 logging.config.fileConfig(self.path_to_ini_file)
88 87 from pylons import config
89 88 add_cache(config)
90 89 engine = engine_from_config(config, 'sqlalchemy.db1.')
91 90 init_model(engine)
92
93 91 index_location = config['index_dir']
94 92 repo_location = self.options.repo_location \
95 93 if self.options.repo_location else RepoModel().repos_path
96 94 repo_list = map(strip, self.options.repo_list.split(',')) \
97 95 if self.options.repo_list else None
98
96 load_rcextensions(config['here'])
99 97 #======================================================================
100 98 # WHOOSH DAEMON
101 99 #======================================================================
@@ -105,7 +103,7 b' class MakeIndex(BasePasterCommand):'
105 103 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
106 104 WhooshIndexingDaemon(index_location=index_location,
107 105 repo_location=repo_location,
108 repo_list=repo_list)\
106 repo_list=repo_list,)\
109 107 .run(full_index=self.options.full_index)
110 108 l.release()
111 109 except LockHeld:
@@ -38,34 +38,17 b' from os.path import join as jn'
38 38 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
39 39 sys.path.append(project_path)
40 40
41
41 from rhodecode.config.conf import INDEX_EXTENSIONS
42 42 from rhodecode.model.scm import ScmModel
43 from rhodecode.lib import safe_unicode
44 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
43 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.indexers import SCHEMA, IDX_NAME
45 45
46 46 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
47 47 NodeDoesNotExistError
48 48
49 49 from whoosh.index import create_in, open_dir
50 50
51
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)
51 log = logging.getLogger('whoosh_indexer')
69 52
70 53
71 54 class WhooshIndexingDaemon(object):
@@ -103,7 +86,8 b' class WhooshIndexingDaemon(object):'
103 86 self.initial = True
104 87
105 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 91 based on repository walk function
108 92 """
109 93 index_paths_ = set()
@@ -127,32 +111,39 b' class WhooshIndexingDaemon(object):'
127 111 return mktime(node.last_changeset.date.timetuple())
128 112
129 113 def add_doc(self, writer, path, repo, repo_name):
130 """Adding doc to writer this function itself fetches data from
131 the instance of vcs backend"""
114 """
115 Adding doc to writer this function itself fetches data from
116 the instance of vcs backend
117 """
118
132 119 node = self.get_node(repo, path)
133
120 indexed = indexed_w_content = 0
134 121 #we just index the content of chosen files, and skip binary files
135 122 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
136
137 123 u_content = node.content
138 124 if not isinstance(u_content, unicode):
139 125 log.warning(' >> %s Could not get this content as unicode '
140 'replacing with empty content', path)
126 'replacing with empty content' % path)
141 127 u_content = u''
142 128 else:
143 129 log.debug(' >> %s [WITH CONTENT]' % path)
130 indexed_w_content += 1
144 131
145 132 else:
146 133 log.debug(' >> %s' % path)
147 134 #just index file name without it's content
148 135 u_content = u''
136 indexed += 1
149 137
150 writer.add_document(owner=unicode(repo.contact),
138 writer.add_document(
139 owner=unicode(repo.contact),
151 140 repository=safe_unicode(repo_name),
152 141 path=safe_unicode(path),
153 142 content=u_content,
154 143 modtime=self.get_node_mtime(node),
155 extension=node.extension)
144 extension=node.extension
145 )
146 return indexed, indexed_w_content
156 147
157 148 def build_index(self):
158 149 if os.path.exists(self.index_location):
@@ -164,19 +155,25 b' class WhooshIndexingDaemon(object):'
164 155
165 156 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
166 157 writer = idx.writer()
167
158 log.debug('BUILDIN INDEX FOR EXTENSIONS %s' % INDEX_EXTENSIONS)
168 159 for repo_name, repo in self.repo_paths.items():
169 160 log.debug('building index @ %s' % repo.path)
170
161 i_cnt = iwc_cnt = 0
171 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 170 log.debug('>> COMMITING CHANGES <<')
175 171 writer.commit(merge=True)
176 172 log.debug('>>> FINISHED BUILDING INDEX <<<')
177 173
178 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 178 idx = open_dir(self.index_location, indexname=self.indexname)
182 179 # The set of all paths in the index
@@ -215,14 +212,19 b' class WhooshIndexingDaemon(object):'
215 212 # Loop over the files in the filesystem
216 213 # Assume we have a function that gathers the filenames of the
217 214 # documents to be indexed
215 ri_cnt = riwc_cnt = 0
218 216 for repo_name, repo in self.repo_paths.items():
219 217 for path in self.get_paths(repo):
220 218 if path in to_index or path not in indexed_paths:
221 219 # This is either a file that's changed, or a new file
222 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 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 228 log.debug('>> COMMITING CHANGES <<')
227 229 writer.commit(merge=True)
228 230 log.debug('>>> FINISHED REBUILDING INDEX <<<')
@@ -27,7 +27,7 b''
27 27 import re
28 28 import logging
29 29
30 from rhodecode.lib import safe_unicode
30 from rhodecode.lib.utils2 import safe_unicode
31 31
32 32 log = logging.getLogger(__name__)
33 33
@@ -23,7 +23,7 b''
23 23 # You should have received a copy of the GNU General Public License
24 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 29 class HttpsFixup(object):
@@ -65,11 +65,11 b' dulserver.DEFAULT_HANDLERS = {'
65 65 }
66 66
67 67 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
68 from dulwich.web import make_wsgi_chain
69 69
70 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 73 from rhodecode.lib.base import BaseVCSController
74 74 from rhodecode.lib.auth import get_container_username
75 75 from rhodecode.lib.utils import is_valid_repo
@@ -86,7 +86,9 b" GIT_PROTO_PAT = re.compile(r'^/(.+)/(inf"
86 86 def is_git(environ):
87 87 path_info = environ['PATH_INFO']
88 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 92 return isgit_path
91 93
92 94
@@ -113,6 +115,10 b' class SimpleGit(BaseVCSController):'
113 115 except:
114 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 123 # GET ACTION PULL or PUSH
118 124 #======================================================================
@@ -121,7 +127,6 b' class SimpleGit(BaseVCSController):'
121 127 #======================================================================
122 128 # CHECK ANONYMOUS PERMISSION
123 129 #======================================================================
124
125 130 if action in ['pull', 'push']:
126 131 anonymous_user = self.__get_user('default')
127 132 username = anonymous_user.username
@@ -177,13 +182,9 b' class SimpleGit(BaseVCSController):'
177 182 #===================================================================
178 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 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 188 try:
188 189 #invalidate cache on push
189 190 if action == 'push':
@@ -204,7 +205,7 b' class SimpleGit(BaseVCSController):'
204 205 """
205 206 _d = {'/' + repo_name: Repo(repo_path)}
206 207 backend = dulserver.DictBackend(_d)
207 gitserve = HTTPGitApplication(backend)
208 gitserve = make_wsgi_chain(backend)
208 209
209 210 return gitserve
210 211
@@ -27,13 +27,14 b''
27 27 import os
28 28 import logging
29 29 import traceback
30 import urllib
30 31
31 32 from mercurial.error import RepoError
32 33 from mercurial.hgweb import hgweb_mod
33 34
34 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 38 from rhodecode.lib.base import BaseVCSController
38 39 from rhodecode.lib.auth import get_container_username
39 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 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 51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 52 """
51 53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
52 55 if http_accept and http_accept.startswith('application/mercurial'):
53 return True
54 return False
56 ishg_path = True
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 66 class SimpleHg(BaseVCSController):
@@ -76,16 +85,20 b' class SimpleHg(BaseVCSController):'
76 85 except:
77 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 93 # GET ACTION PULL or PUSH
81 94 #======================================================================
82 95 action = self.__get_action(environ)
96
83 97 #======================================================================
84 98 # CHECK ANONYMOUS PERMISSION
85 99 #======================================================================
86 100 if action in ['pull', 'push']:
87 101 anonymous_user = self.__get_user('default')
88
89 102 username = anonymous_user.username
90 103 anonymous_perm = self._check_permission(action, anonymous_user,
91 104 repo_name)
@@ -132,30 +145,28 b' class SimpleHg(BaseVCSController):'
132 145 start_response)
133 146
134 147 #check permissions for this repository
135 perm = self._check_permission(action, user,
136 repo_name)
148 perm = self._check_permission(action, user, repo_name)
137 149 if perm is not True:
138 150 return HTTPForbidden()(environ, start_response)
139 151
140 extras = {'ip': ipaddr,
152 # extras are injected into mercurial UI object and later available
153 # in hg hooks executed by rhodecode
154 extras = {
155 'ip': ipaddr,
141 156 'username': username,
142 157 'action': action,
143 'repository': repo_name}
158 'repository': repo_name
159 }
144 160
145 161 #======================================================================
146 162 # MERCURIAL REQUEST HANDLING
147 163 #======================================================================
148
149 repo_path = safe_str(os.path.join(self.basepath, repo_name))
164 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
150 165 log.debug('Repository path is %s' % repo_path)
151 166
152 167 baseui = make_ui('db')
153 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 170 try:
160 171 # invalidate cache on push
161 172 if action == 'push':
@@ -51,9 +51,12 b' from rhodecode.lib.caching_query import '
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
55 CacheInvalidation
55 56 from rhodecode.model.meta import Session
56 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 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 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 70 :param str_: given string
67 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 85 def repo_name_slug(value):
82 """Return slug of name of repository
86 """
87 Return slug of name of repository
83 88 This function is called on each creation/modification
84 89 of repository to prevent bad names in repo
85 90 """
@@ -154,7 +159,10 b' def action_logger(user, action, repo, ip'
154 159 user_log.user_ip = ipaddr
155 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 166 if commit:
159 167 sa.commit()
160 168 except:
@@ -198,12 +206,13 b' def get_repos(path, recursive=False):'
198 206 def is_valid_repo(repo_name, base_path):
199 207 """
200 208 Returns True if given path is a valid repository False otherwise
209
201 210 :param repo_name:
202 211 :param base_path:
203 212
204 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 217 try:
209 218 get_scm(full_path)
@@ -219,7 +228,7 b' def is_valid_repos_group(repos_group_nam'
219 228 :param repo_name:
220 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 233 # check if it's not a repo
225 234 if is_valid_repo(repos_group_name, base_path):
@@ -258,7 +267,8 b" ui_sections = ['alias', 'auth',"
258 267
259 268
260 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 272 and make an mercurial ui object from read options
263 273
264 274 :param path: path to mercurial config file
@@ -371,15 +381,16 b' class EmptyChangeset(BaseChangeset):'
371 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.
377 It returns last group in structure
386 Given a full path to a repository, create all nested groups that this
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 392 sa = meta.Session
382
393 groups = path.split(Repository.url_sep())
383 394 parent = None
384 395 group = None
385 396
@@ -391,22 +402,18 b' def map_groups(groups):'
391 402 group = RepoGroup.get_by_group_name(group_name)
392 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 405 # skip folders that are now removed repos
400 406 if REMOVED_REPO_PAT.match(group_name):
401 407 break
402 408
403 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 412 group = RepoGroup(group_name, parent)
406 413 group.group_description = desc
407 414 sa.add(group)
408 415 rgm._create_default_perms(group)
409 sa.commit()
416 sa.flush()
410 417 parent = group
411 418 return group
412 419
@@ -429,7 +436,7 b' def repo2db_mapper(initial_repo_list, re'
429 436 added = []
430 437
431 438 for name, repo in initial_repo_list.items():
432 group = map_groups(name.split(Repository.url_sep()))
439 group = map_groups(name)
433 440 if not rm.get_by_repo_name(name, cache=False):
434 441 log.info('repository %s not found creating default' % name)
435 442 added.append(name)
@@ -449,10 +456,16 b' def repo2db_mapper(initial_repo_list, re'
449 456 #remove from database those repositories that are not in the filesystem
450 457 for repo in sa.query(Repository).all():
451 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 461 removed.append(repo.repo_name)
453 462 sa.delete(repo)
454 463 sa.commit()
455 464
465 # clear cache keys
466 log.debug("Clearing cache keys now...")
467 CacheInvalidation.clear_cache()
468 sa.commit()
456 469 return added, removed
457 470
458 471
@@ -484,6 +497,30 b' def add_cache(settings):'
484 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 525 # TEST FUNCTIONS AND CREATORS
489 526 #==============================================================================
@@ -624,6 +661,6 b' class BasePasterCommand(Command):'
624 661 """
625 662 from pylons import config as pylonsconfig
626 663
627 path_to_ini_file = os.path.realpath(conf)
628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
664 self.path_to_ini_file = os.path.realpath(conf)
665 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
629 666 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -68,16 +68,21 b' class GitChangeset(BaseChangeset):'
68 68 def branch(self):
69 69 # TODO: Cache as we walk (id <-> branch name mapping)
70 70 refs = self.repository._repo.get_refs()
71 heads = [(key[len('refs/heads/'):], val) for key, val in refs.items()
72 if key.startswith('refs/heads/')]
71 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 80 walker = self.repository._repo.object_store.get_graph_walker([id])
76 81 while True:
77 id = walker.next()
78 if not id:
82 id_ = walker.next()
83 if not id_:
79 84 break
80 if id == self.id:
85 if id_ == self.id:
81 86 return safe_unicode(name)
82 87 raise ChangesetError("This should not happen... Have you manually "
83 88 "change id of the changeset?")
@@ -92,6 +97,7 b' class GitChangeset(BaseChangeset):'
92 97 return path
93 98
94 99 def _get_id_for_path(self, path):
100
95 101 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
96 102 if not path in self._paths:
97 103 path = path.strip('/')
@@ -103,24 +109,23 b' class GitChangeset(BaseChangeset):'
103 109 splitted = path.split('/')
104 110 dirs, name = splitted[:-1], splitted[-1]
105 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 122 for dir in dirs:
107 123 if curdir:
108 124 curdir = '/'.join((curdir, dir))
109 125 else:
110 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 127 dir_id = None
117 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 129 if dir == item:
125 130 dir_id = id
126 131 if dir_id:
@@ -130,6 +135,8 b' class GitChangeset(BaseChangeset):'
130 135 raise ChangesetError('%s is not a directory' % curdir)
131 136 else:
132 137 raise ChangesetError('%s have not been found' % curdir)
138
139 # cache all items from the given traversed tree
133 140 for item, stat, id in tree.iteritems():
134 141 if curdir:
135 142 name = '/'.join((curdir, item))
@@ -137,6 +144,7 b' class GitChangeset(BaseChangeset):'
137 144 name = item
138 145 self._paths[name] = id
139 146 self._stat_modes[name] = stat
147
140 148 if not path in self._paths:
141 149 raise NodeDoesNotExistError("There is no file nor directory "
142 150 "at the given path %r at revision %r"
@@ -85,14 +85,14 b' class BaseModel(object):'
85 85
86 86 if isinstance(instance, cls):
87 87 return instance
88 elif isinstance(instance, int) or str(instance).isdigit():
88 elif isinstance(instance, (int, long)) or str(instance).isdigit():
89 89 return cls.get(instance)
90 90 else:
91 91 if instance:
92 92 if callback is None:
93 93 raise Exception(
94 'given object must be int or Instance of %s got %s, '
95 'no callback provided' % (cls, type(instance))
94 'given object must be int, long or Instance of %s '
95 'got %s, no callback provided' % (cls, type(instance))
96 96 )
97 97 else:
98 98 return callback(instance)
@@ -29,7 +29,7 b' import traceback'
29 29 from pylons.i18n.translation import _
30 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 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
@@ -63,6 +63,7 b' class ChangesetCommentsModel(BaseModel):'
63 63 :param f_path:
64 64 :param line_no:
65 65 """
66
66 67 if text:
67 68 repo = Repository.get(repo_id)
68 69 cs = repo.scm_instance.get_changeset(revision)
@@ -78,7 +79,6 b' class ChangesetCommentsModel(BaseModel):'
78 79
79 80 self.sa.add(comment)
80 81 self.sa.flush()
81
82 82 # make notification
83 83 line = ''
84 84 if line_no:
@@ -39,7 +39,8 b' from rhodecode.lib.vcs.utils.helpers imp'
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 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 44 from rhodecode.lib.compat import json
44 45 from rhodecode.lib.caching_query import FromCache
45 46
@@ -145,12 +146,18 b' class BaseModel(object):'
145 146 obj = cls.query().get(id_)
146 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 155 class RhodeCodeSetting(Base, BaseModel):
150 156 __tablename__ = 'rhodecode_settings'
151 157 __table_args__ = (
152 158 UniqueConstraint('app_settings_name'),
153 {'extend_existing': True}
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
160 'mysql_charset': 'utf8'}
154 161 )
155 162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 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 189 self._app_settings_value = safe_unicode(val)
183 190
184 def __repr__(self):
185 return "<%s('%s:%s')>" % (
191 def __unicode__(self):
192 return u"<%s('%s:%s')>" % (
186 193 self.__class__.__name__,
187 194 self.app_settings_name, self.app_settings_value
188 195 )
@@ -224,7 +231,8 b' class RhodeCodeUi(Base, BaseModel):'
224 231 __tablename__ = 'rhodecode_ui'
225 232 __table_args__ = (
226 233 UniqueConstraint('ui_key'),
227 {'extend_existing': True}
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
235 'mysql_charset': 'utf8'}
228 236 )
229 237
230 238 HOOK_UPDATE = 'changegroup.update'
@@ -274,7 +282,8 b' class User(Base, BaseModel):'
274 282 __tablename__ = 'users'
275 283 __table_args__ = (
276 284 UniqueConstraint('username'), UniqueConstraint('email'),
277 {'extend_existing': True}
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
286 'mysql_charset': 'utf8'}
278 287 )
279 288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 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 303 repositories = relationship('Repository')
295 304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 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 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 316 @hybrid_property
303 317 def email(self):
@@ -328,8 +342,8 b' class User(Base, BaseModel):'
328 342 def is_admin(self):
329 343 return self.admin
330 344
331 def __repr__(self):
332 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
345 def __unicode__(self):
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
333 347 self.user_id, self.username)
334 348
335 349 @classmethod
@@ -376,6 +390,9 b' class User(Base, BaseModel):'
376 390
377 391 def __json__(self):
378 392 return dict(
393 user_id=self.user_id,
394 first_name=self.name,
395 last_name=self.lastname,
379 396 email=self.email,
380 397 full_name=self.full_name,
381 398 full_name_or_username=self.full_name_or_username,
@@ -386,7 +403,10 b' class User(Base, BaseModel):'
386 403
387 404 class UserLog(Base, BaseModel):
388 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 410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
391 411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
392 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 426 class UsersGroup(Base, BaseModel):
407 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 433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 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 437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
415 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):
418 return '<userGroup(%s)>' % (self.users_group_name)
441 def __unicode__(self):
442 return u'<userGroup(%s)>' % (self.users_group_name)
419 443
420 444 @classmethod
421 445 def get_by_group_name(cls, group_name, cache=False,
@@ -443,7 +467,10 b' class UsersGroup(Base, BaseModel):'
443 467
444 468 class UsersGroupMember(Base, BaseModel):
445 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 475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
449 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 488 __tablename__ = 'repositories'
462 489 __table_args__ = (
463 490 UniqueConstraint('repo_name'),
464 {'extend_existing': True},
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
492 'mysql_charset': 'utf8'},
465 493 )
466 494
467 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 518 logs = relationship('UserLog')
491 519
492 def __repr__(self):
493 return "<%s('%s:%s')>" % (self.__class__.__name__,
494 self.repo_id, self.repo_name)
520 def __unicode__(self):
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
522 self.repo_name)
495 523
496 524 @classmethod
497 525 def url_sep(cls):
@@ -710,7 +738,8 b' class RepoGroup(Base, BaseModel):'
710 738 __table_args__ = (
711 739 UniqueConstraint('group_name', 'group_parent_id'),
712 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 744 __mapper_args__ = {'order_by': 'group_name'}
716 745
@@ -728,8 +757,8 b' class RepoGroup(Base, BaseModel):'
728 757 self.group_name = group_name
729 758 self.parent_group = parent_group
730 759
731 def __repr__(self):
732 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
760 def __unicode__(self):
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
733 762 self.group_name)
734 763
735 764 @classmethod
@@ -837,13 +866,16 b' class RepoGroup(Base, BaseModel):'
837 866
838 867 class Permission(Base, BaseModel):
839 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 873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
842 874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
843 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):
846 return "<%s('%s:%s')>" % (
877 def __unicode__(self):
878 return u"<%s('%s:%s')>" % (
847 879 self.__class__.__name__, self.permission_id, self.permission_name
848 880 )
849 881
@@ -874,7 +906,8 b' class UserRepoToPerm(Base, BaseModel):'
874 906 __tablename__ = 'repo_to_perm'
875 907 __table_args__ = (
876 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 912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
880 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 927 Session.add(n)
895 928 return n
896 929
897 def __repr__(self):
898 return '<user:%s => %s >' % (self.user, self.repository)
930 def __unicode__(self):
931 return u'<user:%s => %s >' % (self.user, self.repository)
899 932
900 933
901 934 class UserToPerm(Base, BaseModel):
902 935 __tablename__ = 'user_to_perm'
903 936 __table_args__ = (
904 937 UniqueConstraint('user_id', 'permission_id'),
905 {'extend_existing': True}
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
939 'mysql_charset': 'utf8'}
906 940 )
907 941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
908 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 950 __tablename__ = 'users_group_repo_to_perm'
917 951 __table_args__ = (
918 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 956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 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 971 Session.add(n)
937 972 return n
938 973
939 def __repr__(self):
940 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
974 def __unicode__(self):
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
941 976
942 977
943 978 class UsersGroupToPerm(Base, BaseModel):
944 979 __tablename__ = 'users_group_to_perm'
945 980 __table_args__ = (
946 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 985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 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 994 __tablename__ = 'user_repo_group_to_perm'
959 995 __table_args__ = (
960 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 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 1012 __tablename__ = 'users_group_repo_group_to_perm'
976 1013 __table_args__ = (
977 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 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 1029 class Statistics(Base, BaseModel):
992 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 1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
996 1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
@@ -1006,7 +1048,8 b' class UserFollowing(Base, BaseModel):'
1006 1048 __table_args__ = (
1007 1049 UniqueConstraint('user_id', 'follows_repository_id'),
1008 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 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 1071 class CacheInvalidation(Base, BaseModel):
1029 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 1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1032 1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 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 1085 self.cache_args = cache_args
1039 1086 self.cache_active = False
1040 1087
1041 def __repr__(self):
1042 return "<%s('%s:%s')>" % (self.__class__.__name__,
1088 def __unicode__(self):
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1043 1090 self.cache_id, self.cache_key)
1091 @classmethod
1092 def clear_cache(cls):
1093 cls.query().delete()
1044 1094
1045 1095 @classmethod
1046 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 1100 :param key:
1051 1101 """
@@ -1054,13 +1104,26 b' class CacheInvalidation(Base, BaseModel)'
1054 1104 iid = rhodecode.CONFIG.get('instance_id')
1055 1105 if iid:
1056 1106 prefix = iid
1057 return "%s%s" % (prefix, key)
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1058 1108
1059 1109 @classmethod
1060 1110 def get_by_key(cls, key):
1061 1111 return cls.query().filter(cls.cache_key == key).scalar()
1062 1112
1063 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 1127 def invalidate(cls, key):
1065 1128 """
1066 1129 Returns Invalidation object if this given key should be invalidated
@@ -1069,10 +1132,12 b' class CacheInvalidation(Base, BaseModel)'
1069 1132
1070 1133 :param key:
1071 1134 """
1072 return cls.query()\
1073 .filter(CacheInvalidation.cache_key == key)\
1074 .filter(CacheInvalidation.cache_active == False)\
1075 .scalar()
1135
1136 key, _prefix, _org_key = cls._get_key(key)
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1138
1139 if inv and inv.cache_active is False:
1140 return inv
1076 1141
1077 1142 @classmethod
1078 1143 def set_invalidate(cls, key):
@@ -1082,16 +1147,15 b' class CacheInvalidation(Base, BaseModel)'
1082 1147 :param key:
1083 1148 """
1084 1149
1085 log.debug('marking %s for invalidation' % key)
1086 inv_obj = Session.query(cls)\
1087 .filter(cls.cache_key == key).scalar()
1150 key, _prefix, _org_key = cls._get_key(key)
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1153 _org_key))
1154 try:
1155 for inv_obj in inv_objs:
1088 1156 if inv_obj:
1089 1157 inv_obj.cache_active = False
1090 else:
1091 log.debug('cache key not found in invalidation db -> creating one')
1092 inv_obj = CacheInvalidation(key)
1093 1158
1094 try:
1095 1159 Session.add(inv_obj)
1096 1160 Session.commit()
1097 1161 except Exception:
@@ -1113,7 +1177,10 b' class CacheInvalidation(Base, BaseModel)'
1113 1177
1114 1178 class ChangesetComment(Base, BaseModel):
1115 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 1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1118 1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1119 1186 revision = Column('revision', String(40), nullable=False)
@@ -1142,7 +1209,10 b' class ChangesetComment(Base, BaseModel):'
1142 1209
1143 1210 class Notification(Base, BaseModel):
1144 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 1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1148 1218 TYPE_MESSAGE = u'message'
@@ -1194,7 +1264,8 b' class UserNotification(Base, BaseModel):'
1194 1264 __tablename__ = 'user_to_notification'
1195 1265 __table_args__ = (
1196 1266 UniqueConstraint('user_id', 'notification_id'),
1197 {'extend_existing': True}
1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 'mysql_charset': 'utf8'}
1198 1269 )
1199 1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1200 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 1284 class DbMigrateVersion(Base, BaseModel):
1214 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 1290 repository_id = Column('repository_id', String(250), primary_key=True)
1217 1291 repository_path = Column('repository_path', Text)
1218 1292 version = Column('version', Integer)
@@ -32,6 +32,7 b' import datetime'
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode
35 from rhodecode.config.conf import DATETIME_FORMAT
35 36 from rhodecode.lib import helpers as h
36 37 from rhodecode.model import BaseModel
37 38 from rhodecode.model.db import Notification, User, UserNotification
@@ -47,11 +48,11 b' class NotificationModel(BaseModel):'
47 48 def __get_notification(self, notification):
48 49 if isinstance(notification, Notification):
49 50 return notification
50 elif isinstance(notification, int):
51 elif isinstance(notification, (int, long)):
51 52 return Notification.get(notification)
52 53 else:
53 54 if notification:
54 raise Exception('notification must be int or Instance'
55 raise Exception('notification must be int, long or Instance'
55 56 ' of Notification got %s' % type(notification))
56 57
57 58 def create(self, created_by, subject, body, recipients=None,
@@ -111,6 +112,7 b' class NotificationModel(BaseModel):'
111 112 kwargs.update(email_kwargs)
112 113 email_body_html = EmailNotificationModel()\
113 114 .get_email_tmpl(type_, **kwargs)
115
114 116 run_task(tasks.send_email, rec.email, email_subject, email_body,
115 117 email_body_html)
116 118
@@ -176,14 +178,13 b' class NotificationModel(BaseModel):'
176 178 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
177 179 }
178 180
179 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
180
181 181 tmpl = "%(user)s %(action)s %(when)s"
182 182 if show_age:
183 183 when = h.age(notification.created_on)
184 184 else:
185 185 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
186 186 when = DTF(notification.created_on)
187
187 188 data = dict(
188 189 user=notification.created_by_user.username,
189 190 action=_map[notification.type_], when=when,
@@ -29,15 +29,15 b' import traceback'
29 29 from datetime import datetime
30 30
31 31 from rhodecode.lib.vcs.backends import get_backend
32
33 from rhodecode.lib import LazyProperty
34 from rhodecode.lib import safe_str, safe_unicode
32 from rhodecode.lib.compat import json
33 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
35 34 from rhodecode.lib.caching_query import FromCache
36 35 from rhodecode.lib.hooks import log_create_repository
37 36
38 37 from rhodecode.model import BaseModel
39 38 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 from rhodecode.lib import helpers as h
41 41
42 42
43 43 log = logging.getLogger(__name__)
@@ -95,25 +95,28 b' class RepoModel(BaseModel):'
95 95 return repo.scalar()
96 96
97 97 def get_users_js(self):
98
99 98 users = self.sa.query(User).filter(User.active == True).all()
100 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
101 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
102 u.lastname, u.username)
103 for u in users])
104 return users_array
99 return json.dumps([
100 {
101 'id': u.user_id,
102 'fname': u.name,
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 109 def get_users_groups_js(self):
107 110 users_groups = self.sa.query(UsersGroup)\
108 111 .filter(UsersGroup.users_group_active == True).all()
109 112
110 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
111
112 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
113 (gr.users_group_id, gr.users_group_name,
114 len(gr.members))
115 for gr in users_groups])
116 return users_groups_array
113 return json.dumps([
114 {
115 'id': gr.users_group_id,
116 'grname': gr.users_group_name,
117 'grmembers': len(gr.members),
118 } for gr in users_groups]
119 )
117 120
118 121 def _get_defaults(self, repo_name):
119 122 """
@@ -346,6 +349,7 b' class RepoModel(BaseModel):'
346 349 :param repo: Instance of Repository, repository_id, or repository name
347 350 :param user: Instance of User, user_id or username
348 351 """
352
349 353 user = self.__get_user(user)
350 354 repo = self.__get_repo(repo)
351 355
@@ -28,7 +28,7 b' import logging'
28 28 import traceback
29 29 import shutil
30 30
31 from rhodecode.lib import LazyProperty
31 from rhodecode.lib.utils2 import LazyProperty
32 32
33 33 from rhodecode.model import BaseModel
34 34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
@@ -35,7 +35,7 b' from rhodecode.lib.vcs.nodes import File'
35 35
36 36 from rhodecode import BACKENDS
37 37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import safe_str
38 from rhodecode.lib.utils2 import safe_str
39 39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
@@ -235,13 +235,13 b' class ScmModel(BaseModel):'
235 235 return group_iter
236 236
237 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 240 further global cache invalidation
240 241
241 242 :param repo_name: this repo that should invalidation take place
242 243 """
243 244 CacheInvalidation.set_invalidate(repo_name)
244 CacheInvalidation.set_invalidate(repo_name + "_README")
245 245
246 246 def toggle_following_repo(self, follow_repo_id, user_id):
247 247
@@ -29,18 +29,19 b' import traceback'
29 29 from pylons import url
30 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 33 from rhodecode.lib.caching_query import FromCache
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
39 UsersGroupRepoGroupToPerm
39 40 from rhodecode.lib.exceptions import DefaultUserException, \
40 41 UserOwnsReposException
41 42
42 43 from sqlalchemy.exc import DatabaseError
43 from rhodecode.lib import generate_api_key
44
44 45 from sqlalchemy.orm import joinedload
45 46
46 47 log = logging.getLogger(__name__)
@@ -298,14 +299,16 b' class UserModel(BaseModel):'
298 299 try:
299 300 if user.username == 'default':
300 301 raise DefaultUserException(
301 _("You can't remove this user since it's"
302 " crucial for entire application"))
302 _(u"You can't remove this user since it's"
303 " crucial for entire application")
304 )
303 305 if user.repositories:
304 raise UserOwnsReposException(_('This user still owns %s '
305 'repositories and cannot be '
306 'removed. Switch owners or '
307 'remove those repositories') \
308 % user.repositories)
306 repos = [x.repo_name for x in user.repositories]
307 raise UserOwnsReposException(
308 _(u'user "%s" still owns %s repositories and cannot be '
309 'removed. Switch owners or remove those repositories. %s')
310 % (user.username, len(repos), ', '.join(repos))
311 )
309 312 self.sa.delete(user)
310 313 except:
311 314 log.error(traceback.format_exc())
@@ -409,7 +412,7 b' class UserModel(BaseModel):'
409 412 for perm in default_global_perms:
410 413 user.permissions[GLOBAL].add(perm.permission.permission_name)
411 414
412 # default for repositories
415 # defaults for repositories, taken from default user
413 416 for perm in default_repo_perms:
414 417 r_k = perm.UserRepoToPerm.repository.repo_name
415 418 if perm.Repository.private and not (perm.Repository.user_id == uid):
@@ -423,17 +426,18 b' class UserModel(BaseModel):'
423 426
424 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 431 for perm in default_repo_groups_perms:
428 432 rg_k = perm.UserRepoGroupToPerm.group.group_name
429 433 p = perm.Permission.permission_name
430 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 441 user_perms = self.sa.query(UserToPerm)\
438 442 .options(joinedload(UserToPerm.permission))\
439 443 .filter(UserToPerm.user_id == uid).all()
@@ -441,7 +445,7 b' class UserModel(BaseModel):'
441 445 for perm in user_perms:
442 446 user.permissions[GLOBAL].add(perm.permission.permission_name)
443 447
444 # user repositories
448 # user explicit permissions for repositories
445 449 user_repo_perms = \
446 450 self.sa.query(UserRepoToPerm, Permission, Repository)\
447 451 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
@@ -459,8 +463,8 b' class UserModel(BaseModel):'
459 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
463 # (or replace with higher) permissions
466 # check if user is part of user groups for this repository and
467 # fill in (or replace with higher) permissions
464 468 #==================================================================
465 469
466 470 # users group global
@@ -473,7 +477,7 b' class UserModel(BaseModel):'
473 477 for perm in user_perms_from_users_groups:
474 478 user.permissions[GLOBAL].add(perm.permission.permission_name)
475 479
476 # users group repositories
480 # users group for repositories permissions
477 481 user_repo_perms_from_users_groups = \
478 482 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
479 483 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
@@ -495,12 +499,12 b' class UserModel(BaseModel):'
495 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 503 user_repo_groups_perms = \
500 504 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
501 505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
502 506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
503 .filter(UserRepoToPerm.user_id == uid)\
507 .filter(UserRepoGroupToPerm.user_id == uid)\
504 508 .all()
505 509
506 510 for perm in user_repo_groups_perms:
@@ -510,6 +514,30 b' class UserModel(BaseModel):'
510 514 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
511 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 541 return user
514 542
515 543 def has_perm(self, user, perm):
@@ -185,6 +185,41 b' div.options a {'
185 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 223 #header {
189 224 margin: 0;
190 225 padding: 0 10px;
@@ -197,18 +232,16 b' div.options a {'
197 232 -moz-border-radius: 0px 0px 8px 8px;
198 233 border-radius: 0px 0px 8px 8px;
199 234 height: 37px;
200 background-color: #eedc94;
235 background-color: #003B76;
201 236 background-repeat: repeat-x;
202 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
203 to(#eedc94) );
237 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
204 238 background-image: -moz-linear-gradient(top, #003b76, #00376e);
205 239 background-image: -ms-linear-gradient(top, #003b76, #00376e);
206 240 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
207 241 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
208 242 background-image: -o-linear-gradient(top, #003b76, #00376e);
209 243 background-image: linear-gradient(top, #003b76, #00376e);
210 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
211 endColorstr='#00376e', GradientType=0 );
244 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
212 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 293 min-height: 44px;
261 294 clear: both;
262 295 position: relative;
263 background-color: #eedc94;
296 background-color: #003B76;
264 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 299 background-image: -moz-linear-gradient(top, #003b76, #00376e);
267 300 background-image: -ms-linear-gradient(top, #003b76, #00376e);
268 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 322 -moz-border-radius: 0px 0px 0px 0px;
290 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 333 #header #header-inner #home a {
293 334 height: 40px;
294 335 width: 46px;
@@ -997,9 +1038,9 b' tbody .yui-dt-editable { cursor: pointer'
997 1038 #content div.box div.title {
998 1039 clear: both;
999 1040 overflow: hidden;
1000 background-color: #eedc94;
1041 background-color: #003B76;
1001 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 1044 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1004 1045 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1005 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 1801 #footer div#footer-inner {
1761 background-color: #eedc94; background-repeat : repeat-x;
1762 background-image : -khtml-gradient( linear, left top, left bottom,
1763 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1764 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1765 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1766 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1802 background-color: #003B76;
1803 background-repeat : repeat-x;
1804 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1805 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1806 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1807 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1767 1808 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1768 1809 background-image : -o-linear-gradient( top, #003b76, #00376e));
1769 background-image : linear-gradient( top, #003b76, #00376e); filter :
1770 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1771 '#003b76', endColorstr = '#00376e', GradientType = 0);
1810 background-image : linear-gradient( top, #003b76, #00376e);
1811 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1772 1812 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1773 1813 -webkit-border-radius: 4px 4px 4px 4px;
1774 1814 -khtml-border-radius: 4px 4px 4px 4px;
1775 1815 -moz-border-radius: 4px 4px 4px 4px;
1776 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 1819 #footer div#footer-inner p {
@@ -1808,30 +1837,18 b' div.form div.fields div.field div.button'
1808 1837 clear: both;
1809 1838 overflow: hidden;
1810 1839 position: relative;
1811 background-color: #eedc94; background-repeat : repeat-x;
1812 background-image : -khtml-gradient( linear, left top, left bottom,
1813 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1814 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1815 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1816 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1817 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1818 background-image : -o-linear-gradient( top, #003b76, #00376e));
1819 background-image : linear-gradient( top, #003b76, #00376e); filter :
1820 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1821 '#003b76', endColorstr = '#00376e', GradientType = 0);
1822 margin: 0 auto;
1823 padding: 0;
1840 background-color: #003B76;
1824 1841 background-repeat: repeat-x;
1825 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1826 to(#eedc94) );
1842 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1827 1843 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1828 1844 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1829 1845 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);
1846 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1847 background-image : -o-linear-gradient( top, #003b76, #00376e));
1832 1848 background-image: linear-gradient(top, #003b76, #00376e);
1833 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1834 endColorstr='#00376e', GradientType=0 );
1849 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1850 margin: 0 auto;
1851 padding: 0;
1835 1852 }
1836 1853
1837 1854 #login div.inner {
@@ -1908,16 +1925,14 b' div.form div.fields div.field div.button'
1908 1925 width: 278px;
1909 1926
1910 1927 background-repeat: repeat-x;
1911 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1912 to(#eedc94) );
1928 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1913 1929 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1914 1930 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1915 1931 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1916 1932 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1917 1933 background-image: -o-linear-gradient(top, #003b76, #00376e);
1918 1934 background-image: linear-gradient(top, #003b76, #00376e);
1919 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1920 endColorstr='#00376e', GradientType=0 );
1935 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1921 1936
1922 1937 z-index: 999;
1923 1938 -webkit-border-radius: 0px 0px 4px 4px;
@@ -2060,10 +2075,9 b' div.form div.fields div.field div.button'
2060 2075 clear: both;
2061 2076 overflow: hidden;
2062 2077 position: relative;
2063 background-color: #eedc94;
2078 background-color: #003B76;
2064 2079 background-repeat: repeat-x;
2065 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
2066 to(#eedc94) );
2080 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2067 2081 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2068 2082 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2069 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 2810 .ac .yui-ac {
2797 position: relative;
2811 position: inherit;
2798 2812 font-size: 100%;
2799 2813 }
2800 2814
2801 2815 .ac .perm_ac {
2802 width: 15em;
2816 width: 20em;
2803 2817 }
2804 2818
2805 2819 .ac .yui-ac-input {
@@ -2809,16 +2823,15 b' table.code-browser .browser-dir {'
2809 2823 .ac .yui-ac-container {
2810 2824 position: absolute;
2811 2825 top: 1.6em;
2812 width: 100%;
2826 width: auto;
2813 2827 }
2814 2828
2815 2829 .ac .yui-ac-content {
2816 2830 position: absolute;
2817 width: 100%;
2818 2831 border: 1px solid gray;
2819 2832 background: #fff;
2820 overflow: hidden;
2821 2833 z-index: 9050;
2834
2822 2835 }
2823 2836
2824 2837 .ac .yui-ac-shadow {
@@ -2836,6 +2849,7 b' table.code-browser .browser-dir {'
2836 2849 width: 100%;
2837 2850 margin: 0;
2838 2851 padding: 0;
2852 z-index: 9050;
2839 2853 }
2840 2854
2841 2855 .ac .yui-ac-content li {
@@ -2843,15 +2857,28 b' table.code-browser .browser-dir {'
2843 2857 white-space: nowrap;
2844 2858 margin: 0;
2845 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 2870 .ac .yui-ac-content li.yui-ac-prehighlight {
2849 2871 background: #B3D4FF;
2872 z-index: 9050;
2850 2873 }
2851 2874
2852 2875 .ac .yui-ac-content li.yui-ac-highlight {
2853 2876 background: #556CB5;
2854 2877 color: #FFF;
2878 z-index: 9050;
2879 }
2880 .ac .yui-ac-bd{
2881 z-index: 9050;
2855 2882 }
2856 2883
2857 2884 .follow {
@@ -3006,17 +3033,14 b' table.code-browser .browser-dir {'
3006 3033 .error_msg {
3007 3034 background-color: #c43c35;
3008 3035 background-repeat: repeat-x;
3009 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b),
3010 to(#c43c35) );
3036 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3011 3037 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3012 3038 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3013 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b),
3014 color-stop(100%, #c43c35) );
3039 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3015 3040 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3016 3041 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3017 3042 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3018 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',
3019 endColorstr='#c43c35', GradientType=0 );
3043 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3020 3044 border-color: #c43c35 #c43c35 #882a25;
3021 3045 }
3022 3046
@@ -3024,51 +3048,42 b' table.code-browser .browser-dir {'
3024 3048 color: #404040 !important;
3025 3049 background-color: #eedc94;
3026 3050 background-repeat: repeat-x;
3027 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
3028 to(#eedc94) );
3051 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3029 3052 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3030 3053 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3031 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1),
3032 color-stop(100%, #eedc94) );
3054 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3033 3055 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3034 3056 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3035 3057 background-image: linear-gradient(top, #fceec1, #eedc94);
3036 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1',
3037 endColorstr='#eedc94', GradientType=0 );
3058 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3038 3059 border-color: #eedc94 #eedc94 #e4c652;
3039 3060 }
3040 3061
3041 3062 .success_msg {
3042 3063 background-color: #57a957;
3043 3064 background-repeat: repeat-x !important;
3044 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462),
3045 to(#57a957) );
3065 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3046 3066 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3047 3067 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3048 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462),
3049 color-stop(100%, #57a957) );
3068 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3050 3069 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3051 3070 background-image: -o-linear-gradient(top, #62c462, #57a957);
3052 3071 background-image: linear-gradient(top, #62c462, #57a957);
3053 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',
3054 endColorstr='#57a957', GradientType=0 );
3072 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3055 3073 border-color: #57a957 #57a957 #3d773d;
3056 3074 }
3057 3075
3058 3076 .notice_msg {
3059 3077 background-color: #339bb9;
3060 3078 background-repeat: repeat-x;
3061 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de),
3062 to(#339bb9) );
3079 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3063 3080 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3064 3081 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3065 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de),
3066 color-stop(100%, #339bb9) );
3082 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3067 3083 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3068 3084 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3069 3085 background-image: linear-gradient(top, #5bc0de, #339bb9);
3070 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',
3071 endColorstr='#339bb9', GradientType=0 );
3086 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3072 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 3113 #msg_close {
3099 background: transparent url("../icons/cross_grey_small.png") no-repeat
3100 scroll 0 0;
3114 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3101 3115 cursor: pointer;
3102 3116 height: 16px;
3103 3117 position: absolute;
@@ -3105,7 +3119,12 b' table.code-browser .browser-dir {'
3105 3119 top: 5px;
3106 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 3128 div#legend_container table,div#legend_choices table {
3110 3129 width: auto !important;
3111 3130 }
@@ -4115,6 +4134,56 b' form.comment-inline-form {'
4115 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 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 785 * QUICK REPO MENU
614 786 */
@@ -700,6 +872,19 b' var nameSort = function(a, b, desc, fiel'
700 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 888 var groupNameSort = function(a, b, desc, field) {
704 889 var a_ = fromHTML(a.getData(field));
705 890 var b_ = fromHTML(b.getData(field));
@@ -40,7 +40,14 b''
40 40 </ul>
41 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 51 <div style="white-space: nowrap">
45 52 ##TYPE OF REPO
46 53 %if h.is_hg(rtype):
@@ -57,7 +64,11 b''
57 64 %endif
58 65
59 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 72 %if fork_of:
62 73 <a href="${h.url('summary_home',repo_name=fork_of)}">
63 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 21 </div>
22 22 <div class="input">
23 23 ${h.text('clone_uri',class_="small")}
24 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
24 25 </div>
25 26 </div>
26 27 <div class="field">
@@ -28,7 +29,8 b''
28 29 <label for="repo_group">${_('Repository group')}:</label>
29 30 </div>
30 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 34 </div>
33 35 </div>
34 36 <div class="field">
@@ -37,6 +39,7 b''
37 39 </div>
38 40 <div class="input">
39 41 ${h.select('repo_type','hg',c.backends,class_="small")}
42 <span class="help-block">${_('Type of repository to create.')}</span>
40 43 </div>
41 44 </div>
42 45 <div class="field">
@@ -44,15 +47,17 b''
44 47 <label for="description">${_('Description')}:</label>
45 48 </div>
46 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 52 </div>
49 53 </div>
50 54 <div class="field">
51 55 <div class="label label-checkbox">
52 <label for="private">${_('Private')}:</label>
56 <label for="private">${_('Private repository')}:</label>
53 57 </div>
54 58 <div class="checkboxes">
55 59 ${h.checkbox('private',value="True")}
60 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
56 61 </div>
57 62 </div>
58 63 <div class="buttons">
@@ -41,6 +41,7 b''
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 45 </div>
45 46 </div>
46 47 <div class="field">
@@ -49,6 +50,7 b''
49 50 </div>
50 51 <div class="input">
51 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 54 </div>
53 55 </div>
54 56 <div class="field">
@@ -64,16 +66,18 b''
64 66 <label for="description">${_('Description')}:</label>
65 67 </div>
66 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 71 </div>
69 72 </div>
70 73
71 74 <div class="field">
72 75 <div class="label label-checkbox">
73 <label for="private">${_('Private')}:</label>
76 <label for="private">${_('Private repository')}:</label>
74 77 </div>
75 78 <div class="checkboxes">
76 79 ${h.checkbox('private',value="True")}
80 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
77 81 </div>
78 82 </div>
79 83 <div class="field">
@@ -82,6 +86,7 b''
82 86 </div>
83 87 <div class="checkboxes">
84 88 ${h.checkbox('enable_statistics',value="True")}
89 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
85 90 </div>
86 91 </div>
87 92 <div class="field">
@@ -90,15 +95,17 b''
90 95 </div>
91 96 <div class="checkboxes">
92 97 ${h.checkbox('enable_downloads',value="True")}
98 <span class="help-block">${_('Enable download menu on summary page.')}</span>
93 99 </div>
94 100 </div>
95 101 <div class="field">
96 102 <div class="label">
97 103 <label for="user">${_('Owner')}:</label>
98 104 </div>
99 <div class="input input-small ac">
105 <div class="input input-medium ac">
100 106 <div class="perm_ac">
101 107 ${h.text('user',class_='yui-ac-input')}
108 <span class="help-block">${_('Change owner of this repository.')}</span>
102 109 <div id="owner_container"></div>
103 110 </div>
104 111 </div>
@@ -25,7 +25,7 b''
25 25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 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 29 </td>
30 30 <td>
31 31 %if r2p.user.username !='default':
@@ -46,7 +46,7 b''
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 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 50 </td>
51 51 <td>
52 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 117 YUD.setStyle('add_perm', 'opacity', '0.6');
118 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 128 </script>
@@ -48,7 +48,7 b''
48 48 ${dt.quick_menu(repo['name'])}
49 49 </td>
50 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 52 </td>
53 53 ##DESCRIPTION
54 54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
@@ -15,7 +15,7 b''
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 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 19 </td>
20 20 <td>
21 21 %if r2p.user.username !='default':
@@ -35,7 +35,7 b''
35 35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 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 39 </td>
40 40 <td>
41 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 68 </table>
69 69 <script type="text/javascript">
70 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 72 var callback = {
73 73 success: function (o) {
74 74 var tr = YUD.get(String(field_id));
@@ -83,7 +83,7 b' function ajaxActionUser(user_id, field_i'
83 83 };
84 84
85 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 87 var callback = {
88 88 success:function(o){
89 89 var tr = YUD.get(String(field_id));
@@ -106,165 +106,12 b' YUE.onDOMReady(function () {'
106 106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 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 117 </script>
@@ -17,5 +17,5 b''
17 17 ${self.menu('admin')}
18 18 </%def>
19 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 21 </%def>
@@ -44,7 +44,7 b''
44 44 <td>
45 45 <div style="white-space: nowrap">
46 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 48 </div>
49 49 </td>
50 50 <td>${gr.group_description}</td>
@@ -113,52 +113,45 b''
113 113 <div class="title">
114 114 <h5>
115 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 117 </h5>
118 118 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
119 119 <ul class="links">
120 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 122 </li>
123 123 </ul>
124 124 %endif
125 125 </div>
126 126 <!-- end box / title -->
127 <div class="table">
128 <table>
127 <div id="my" class="table">
128 <div id='repos_list_wrap' class="yui-skin-sam">
129 <table id="repos_list">
129 130 <thead>
130 131 <tr>
132 <th></th>
131 133 <th class="left">${_('Name')}</th>
132 <th class="left">${_('revision')}</th>
133 <th colspan="2" class="left">${_('action')}</th>
134 <th class="left">${_('Revision')}</th>
135 <th class="left">${_('Action')}</th>
136 <th class="left">${_('Action')}</th>
134 137 </thead>
135 138 <tbody>
139 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
136 140 %if c.user_repos:
137 141 %for repo in c.user_repos:
138 142 <tr>
143 ##QUICK MENU
144 <td class="quick_repo_menu">
145 ${dt.quick_menu(repo['name'])}
146 </td>
147 ##REPO NAME AND ICONS
148 <td class="reponame">
149 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
150 </td>
151 ##LAST REVISION
139 152 <td>
140 %if h.is_hg(repo['dbrepo']['repo_type']):
141 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
142 %elif h.is_git(repo['dbrepo']['repo_type']):
143 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
144 %else:
145
146 %endif
147 %if repo['dbrepo']['private']:
148 <img class="icon" alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
149 %else:
150 <img class="icon" alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
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
153 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
160 154 </td>
161 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
162 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 156 <td>
164 157 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
@@ -179,12 +172,142 b''
179 172 </table>
180 173 </div>
181 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
220 </div>
221 </div>
182 222 <script type="text/javascript">
183 var nodes = YUQ('div.table tr td a.repo_name');
184 var target = 'q_filter';
223 var filter_activate = function(){
224 var nodes = YUQ('#my tr td a.repo_name');
185 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 312 </script>
190 313 </%def>
@@ -37,7 +37,7 b''
37 37 %for cnt,u_group in enumerate(c.users_groups_list):
38 38 <tr class="parity${cnt%2}">
39 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 41 <td>${h.bool2icon(u_group.users_group_active)}</td>
42 42 <td>
43 43 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
@@ -1,5 +1,5 b''
1 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 3 <html xmlns="http://www.w3.org/1999/xhtml">
4 4 <head>
5 5 <title>${self.title()}</title>
@@ -37,7 +37,7 b''
37 37 ## JAVASCRIPT ##
38 38 <%def name="js()">
39 39 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
40 <!--[if IE]>
40 <!--[if lt IE 9]>
41 41 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
42 42 <![endif]-->
43 43 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
@@ -130,6 +130,17 b''
130 130 ${self.js()}
131 131 </head>
132 132 <body id="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
133 144 ${next.body()}
134 145 </body>
135 146 </html>
@@ -93,11 +93,11 b''
93 93 %endif
94 94 %if h.is_hg(c.rhodecode_repo) and cs.branch:
95 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 97 %endif
98 98 %for tag in cs.tags:
99 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 101 %endfor
102 102 </span>
103 103 </div>
@@ -36,8 +36,8 b''
36 36 <div class="diff-actions">
37 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 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()}
40 ${c.context_url()}
39 ${c.ignorews_url(request.GET)}
40 ${c.context_url(request.GET)}
41 41 </div>
42 42 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
43 43 </div>
@@ -91,14 +91,14 b''
91 91 </div>
92 92 </div>
93 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 95 </span>
96 96 <div class="cs_files">
97 97 %for change,filenode,diff,cs1,cs2,stat in c.changes:
98 98 <div class="cs_${change}">
99 99 <div class="node">
100 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 102 %else:
103 103 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
104 104 %endif
@@ -7,7 +7,7 b''
7 7
8 8 %for change,filenode,diff,cs1,cs2,stat in changes:
9 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 11 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm">
12 12 <div class="code-header">
13 13 <div class="changeset_header">
@@ -19,8 +19,8 b''
19 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 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 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))}
23 ${c.context_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(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
24 24 </div>
25 25 <span style="float:right;margin-top:-3px">
26 26 <label>
@@ -9,7 +9,11 b''
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 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 15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 %endif
13 17 </li>
14 18 </ul>
15 19 %endif
@@ -77,7 +81,7 b''
77 81 </td>
78 82 ##REPO NAME AND ICONS
79 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 85 </td>
82 86 ##DESCRIPTION
83 87 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
@@ -115,7 +119,7 b''
115 119 </div>
116 120 </div>
117 121 <script>
118 YUD.get('repo_count').innerHTML = ${cnt+1};
122 YUD.get('repo_count').innerHTML = ${cnt};
119 123 var func = function(node){
120 124 return node.parentNode.parentNode.parentNode.parentNode;
121 125 }
@@ -48,7 +48,7 b''
48 48 <tr>
49 49 <th></th>
50 50 <th class="left">${_('Name')}</th>
51 <th class="left">${_('Tip')}</th>
51 <th class="left">${_('Revision')}</th>
52 52 <th class="left">${_('Action')}</th>
53 53 <th class="left">${_('Action')}</th>
54 54 </thead>
@@ -36,10 +36,20 b''
36 36 </div>
37 37 <div class="field">
38 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>
46 <div class="field">
47 <div class="label">
39 48 <label for="repo_group">${_('Repository group')}:</label>
40 49 </div>
41 50 <div class="input">
42 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 53 </div>
44 54 </div>
45 55 <div class="field">
@@ -47,16 +57,18 b''
47 57 <label for="description">${_('Description')}:</label>
48 58 </div>
49 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 62 </div>
52 63 </div>
53 64
54 65 <div class="field">
55 66 <div class="label label-checkbox">
56 <label for="private">${_('Private')}:</label>
67 <label for="private">${_('Private repository')}:</label>
57 68 </div>
58 69 <div class="checkboxes">
59 70 ${h.checkbox('private',value="True")}
71 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
60 72 </div>
61 73 </div>
62 74
@@ -2,7 +2,8 b' from rhodecode.tests import *'
2 2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 3 UserNotification
4 4
5 class TestChangeSetCommentrController(TestController):
5
6 class TestChangeSetCommentsController(TestController):
6 7
7 8 def setUp(self):
8 9 for x in ChangesetComment.query().all():
@@ -42,13 +43,18 b' class TestChangeSetCommentrController(Te'
42 43 self.assertTrue('''<div class="comments-number">%s '''
43 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 49 notification = Notification.query().all()[0]
48 50
49 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
50 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
51 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
51 ID = ChangesetComment.query().first().comment_id
52 self.assertEqual(notification.type_,
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 59 def test_create_inline(self):
54 60 self.log_user()
@@ -76,11 +82,16 b' class TestChangeSetCommentrController(Te'
76 82 '''repositorypy">''' in response.body)
77 83
78 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)
82 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
83 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
87 notification = Notification.query().all()[0]
88 ID = ChangesetComment.query().first().comment_id
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 96 def test_create_with_mention(self):
86 97 self.log_user()
@@ -103,7 +114,6 b' class TestChangeSetCommentrController(Te'
103 114 self.assertTrue('''<div class="comments-number">%s '''
104 115 '''comment(s) (0 inline)</div>''' % 1 in response.body)
105 116
106
107 117 self.assertEqual(Notification.query().count(), 2)
108 118 users = [x.user.username for x in UserNotification.query().all()]
109 119
@@ -124,7 +134,6 b' class TestChangeSetCommentrController(Te'
124 134 self.assertEqual(len(comments), 1)
125 135 comment_id = comments[0].comment_id
126 136
127
128 137 self.app.delete(url(controller='changeset',
129 138 action='delete_comment',
130 139 repo_name=HG_REPO,
@@ -1,10 +1,11 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 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 5 from rhodecode.lib.auth import check_password
6 6 from rhodecode.model.meta import Session
7 7
8
8 9 class TestLoginController(TestController):
9 10
10 11 def tearDown(self):
@@ -65,22 +65,20 b' TEST_URLS += ['
65 65
66 66 class TestLibs(unittest.TestCase):
67 67
68
69 68 def test_uri_filter(self):
70 from rhodecode.lib import uri_filter
69 from rhodecode.lib.utils2 import uri_filter
71 70
72 71 for url in TEST_URLS:
73 72 self.assertEqual(uri_filter(url[0]), url[1])
74 73
75 74 def test_credentials_filter(self):
76 from rhodecode.lib import credentials_filter
75 from rhodecode.lib.utils2 import credentials_filter
77 76
78 77 for url in TEST_URLS:
79 78 self.assertEqual(credentials_filter(url[0]), url[2])
80 79
81
82 80 def test_str2bool(self):
83 from rhodecode.lib import str2bool
81 from rhodecode.lib.utils2 import str2bool
84 82 test_cases = [
85 83 ('t', True),
86 84 ('true', True),
@@ -103,9 +101,8 b' class TestLibs(unittest.TestCase):'
103 101 for case in test_cases:
104 102 self.assertEqual(str2bool(case[0]), case[1])
105 103
106
107 104 def test_mention_extractor(self):
108 from rhodecode.lib import extract_mentioned_users
105 from rhodecode.lib.utils2 import extract_mentioned_users
109 106 sample = ("@first hi there @marcink here's my email marcin@email.com "
110 107 "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
111 108 "@MARCIN @maRCiN @2one_more22")
@@ -5,7 +5,7 b' from rhodecode.tests import *'
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 UsersGroup, UsersGroupMember, Permission
8 UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm
9 9 from sqlalchemy.exc import IntegrityError
10 10 from rhodecode.model.user import UserModel
11 11
@@ -430,6 +430,11 b' class TestPermissions(unittest.TestCase)'
430 430 username=u'u1', password=u'qweqwe',
431 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 438 self.a1 = UserModel().create_or_update(
434 439 username=u'a1', password=u'qweqwe',
435 440 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
@@ -437,7 +442,10 b' class TestPermissions(unittest.TestCase)'
437 442 Session.commit()
438 443
439 444 def tearDown(self):
445 if hasattr(self, 'test_repo'):
446 RepoModel().delete(repo=self.test_repo)
440 447 UserModel().delete(self.u1)
448 UserModel().delete(self.u2)
441 449 UserModel().delete(self.a1)
442 450 if hasattr(self, 'g1'):
443 451 ReposGroupModel().delete(self.g1.group_id)
@@ -578,3 +586,130 b' class TestPermissions(unittest.TestCase)'
578 586 new_perm_h)
579 587 self.assertEqual(u1_auth.permissions['repositories_groups'],
580 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'})
@@ -95,6 +95,7 b' setup('
95 95
96 96 [paste.global_paster_command]
97 97 make-index = rhodecode.lib.indexers:MakeIndex
98 make-rcext=rhodecode.config.rcextensions.make_rcextensions:MakeRcExt
98 99 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
99 100 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
100 101 """,
@@ -17,6 +17,7 b' pdebug = false'
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 #email_prefix = [RhodeCode]
20 21
21 22 #smtp_server = mail.server.com
22 23 #smtp_username =
@@ -24,6 +25,8 b' pdebug = false'
24 25 #smtp_port =
25 26 #smtp_use_tls = false
26 27 #smtp_use_ssl = true
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
27 30
28 31 [server:main]
29 32 ##nr of threads to spawn
@@ -54,6 +57,42 b' use_gravatar = true'
54 57 container_auth_enabled = false
55 58 proxypass_auth_enabled = false
56 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 =
95
57 96 ####################################
58 97 ### CELERY CONFIG ####
59 98 ####################################
@@ -86,6 +125,7 b' celery.always.eager = false'
86 125 ####################################
87 126 beaker.cache.data_dir=/tmp/data/cache/data
88 127 beaker.cache.lock_dir=/tmp/data/cache/lock
128
89 129 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90 130
91 131 beaker.cache.super_short_term.type=memory
@@ -119,11 +159,26 b' beaker.cache.sql_cache_long.key_length ='
119 159 ## dbm, file, memcached, database, and memory.
120 160 ## The storage uses the Container API
121 161 ##that is also used by the cache system.
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
171
122 172 beaker.session.type = file
123
124 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 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 183 ##auto save the session to not to use .save()
129 184 beaker.session.auto = False
@@ -151,15 +206,17 b' logview.pylons.util = #eee'
151 206 #########################################################
152 207 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
153 208 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
154 #sqlalchemy.db1.echo = false
155 #sqlalchemy.db1.pool_recycle = 3600
156 sqlalchemy.convert_unicode = true
209 #sqlalchemy.db1.url = mysql://root:qwe123qwe@localhost/rhodecode_tests
210
211 sqlalchemy.db1.echo = false
212 sqlalchemy.db1.pool_recycle = 3600
213 sqlalchemy.db1.convert_unicode = true
157 214
158 215 ################################
159 216 ### LOGGING CONFIGURATION ####
160 217 ################################
161 218 [loggers]
162 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
219 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
163 220
164 221 [handlers]
165 222 keys = console
@@ -205,6 +262,12 b' handlers = console'
205 262 qualname = sqlalchemy.engine
206 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 272 ## HANDLERS ##
210 273 ##############
General Comments 0
You need to be logged in to leave comments. Login now