##// END OF EJS Templates
Merge with upstream
Liad Shani -
r1615:51bd5404 merge beta
parent child Browse files
Show More
@@ -0,0 +1,132 b''
1 #!/bin/sh
2 ########################################
3 #### THIS IS A REDHAT INIT.D SCRIPT ####
4 ########################################
5
6 ##################################################
7 #
8 # RhodeCode server startup script
9 # Recommended default-startup: 2 3 4 5
10 # Recommended default-stop: 0 1 6
11 #
12 ##################################################
13
14
15 APP_NAME="rhodecode"
16 # the location of your app
17 # since this is a web app, it should go in /var/www
18 APP_PATH="/var/www/$APP_NAME"
19
20 CONF_NAME="production.ini"
21
22 # write to wherever the PID should be stored, just ensure
23 # that the user you run paster as has the appropriate permissions
24 # same goes for the log file
25 PID_PATH="/var/run/rhodecode/pid"
26 LOG_PATH="/var/log/rhodecode/rhodecode.log"
27
28 # replace this with the path to the virtual environment you
29 # made for RhodeCode
30 PYTHON_PATH="/opt/python_virtualenvironments/rhodecode-venv"
31
32 RUN_AS="rhodecode"
33
34 DAEMON="$PYTHON_PATH/bin/paster"
35
36 DAEMON_OPTS="serve --daemon \
37 --user=$RUN_AS \
38 --group=$RUN_AS \
39 --pid-file=$PID_PATH \
40 --log-file=$LOG_PATH $APP_PATH/$CONF_NAME"
41
42 DESC="rhodecode-server"
43 LOCK_FILE="/var/lock/subsys/$APP_NAME"
44
45 # source CentOS init functions
46 . /etc/init.d/functions
47
48 RETVAL=0
49
50 remove_pid () {
51 rm -f ${PID_PATH}
52 rmdir `dirname ${PID_PATH}`
53 }
54
55 ensure_pid_dir () {
56 PID_DIR=`dirname ${PID_PATH}`
57 if [ ! -d ${PID_DIR} ] ; then
58 mkdir -p ${PID_DIR}
59 chown -R ${RUN_AS}:${RUN_AS} ${PID_DIR}
60 chmod 755 ${PID_DIR}
61 fi
62 }
63
64 start_rhodecode () {
65 ensure_pid_dir
66 PYTHON_EGG_CACHE="/tmp" daemon --pidfile $PID_PATH \
67 --user $RUN_AS "$DAEMON $DAEMON_OPTS"
68 RETVAL=$?
69 [ $RETVAL -eq 0 ] && touch $LOCK_FILE
70 return $RETVAL
71 }
72
73 stop_rhodecode () {
74 if [ -e $LOCK_FILE ]; then
75 killproc -p $PID_PATH
76 RETVAL=$?
77 rm -f $LOCK_FILE
78 rm -f $PID_PATH
79 else
80 RETVAL=1
81 fi
82 return $RETVAL
83 }
84
85 status_rhodecode() {
86 if [ -e $LOCK_FILE ]; then
87 # exit with non-zero to indicate failure
88 RETVAL=1
89 else
90 RETVAL=0
91 fi
92 return $RETVAL
93 }
94
95 restart_rhodecode () {
96 stop_rhodecode
97 start_rhodecode
98 RETVAL=$?
99 }
100
101 case "$1" in
102 start)
103 echo -n $"Starting $DESC: "
104 start_rhodecode
105 echo
106 ;;
107 stop)
108 echo -n $"Stopping $DESC: "
109 stop_rhodecode
110 echo
111 ;;
112 status)
113 status_rhodecode
114 RETVAL=$?
115 if [ ! $RETVAL -eq 0 ]; then
116 echo "RhodeCode server is running..."
117 else
118 echo "RhodeCode server is stopped."
119 fi
120 ;;
121 restart)
122 echo -n $"Restarting $DESC: "
123 restart_rhodecode
124 echo
125 ;;
126 *)
127 echo $"Usage: $0 {start|stop|restart|status}"
128 RETVAL=1
129 ;;
130 esac
131
132 exit $RETVAL No newline at end of file
@@ -4,6 +4,7 b' List of contributors to RhodeCode projec'
4 4 Jason Harris <jason@jasonfharris.com>
5 5 Thayne Harbaugh <thayne@fusionio.com>
6 6 cejones
7 Thomas Waldmann <tw-public@gmx.de>
7 8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 9 Dmitri Kuznetsov
9 10 Jared Bunting <jared.bunting@peachjean.com>
@@ -11,4 +12,4 b' List of contributors to RhodeCode projec'
11 12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 13 Ankit Solanki <ankit.solanki@gmail.com>
13 14 Liad Shani <liadff@gmail.com>
14
15 Les Peabody <lpeabody@gmail.com>
@@ -13,9 +13,34 b' 1.3.0 (**XXXX-XX-XX**)'
13 13 news
14 14 ----
15 15
16
16 17 fixes
17 18 -----
18 19
20
21 1.2.2 (**2011-10-17**)
22 ======================
23
24 news
25 ----
26
27 - #226 repo groups are available by path instead of numerical id
28
29 fixes
30 -----
31
32 - #259 Groups with the same name but with different parent group
33 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
34 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
35 - #265 ldap save fails sometimes on converting attributes to booleans,
36 added getter and setter into model that will prevent from this on db model level
37 - fixed problems with timestamps issues #251 and #213
38 - fixes #266 Rhodecode allows to create repo with the same name and in
39 the same parent as group
40 - fixes #245 Rescan of the repositories on Windows
41 - fixes #248 cannot edit repos inside a group on windows
42 - fixes #219 forking problems on windows
43
19 44 1.2.1 (**2011-10-08**)
20 45 ======================
21 46
@@ -443,8 +443,8 b' in the production.ini file::'
443 443 In order to not have the statics served by the application. This improves speed.
444 444
445 445
446 Apache virtual host example
447 ---------------------------
446 Apache virtual host reverse proxy example
447 -----------------------------------------
448 448
449 449 Here is a sample configuration file for apache using proxy::
450 450
@@ -503,6 +503,31 b' then change <someprefix> into your choos'
503 503 Apache's WSGI config
504 504 --------------------
505 505
506 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
507 that, you'll need to:
508
509 - Install mod_wsgi. If using a Debian-based distro, you can install
510 the package libapache2-mod-wsgi::
511
512 aptitude install libapache2-mod-wsgi
513
514 - Enable mod_wsgi::
515
516 a2enmod wsgi
517
518 - Create a wsgi dispatch script, like the one below. Make sure you
519 check the paths correctly point to where you installed RhodeCode
520 and its Python Virtual Environment.
521 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
522 as in the following example. Once again, check the paths are
523 correctly specified.
524
525 Here is a sample excerpt from an Apache Virtual Host configuration file::
526
527 WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \
528 threads=4 \
529 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
530 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
506 531
507 532 Example wsgi dispatch script::
508 533
@@ -512,6 +537,9 b' Example wsgi dispatch script::'
512 537
513 538 # sometimes it's needed to set the curent dir
514 539 os.chdir('/home/web/rhodecode/')
540
541 import site
542 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
515 543
516 544 from paste.deploy import loadapp
517 545 from paste.script.util.logging_config import fileConfig
@@ -519,6 +547,10 b' Example wsgi dispatch script::'
519 547 fileConfig('/home/web/rhodecode/production.ini')
520 548 application = loadapp('config:/home/web/rhodecode/production.ini')
521 549
550 Note: when using mod_wsgi you'll need to install the same version of
551 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
552 environment.
553
522 554
523 555 Other configuration files
524 556 -------------------------
@@ -35,7 +35,7 b" PLATFORM_WIN = ('Windows')"
35 35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
36 36
37 37 try:
38 from rhodecode.lib.utils import get_current_revision
38 from rhodecode.lib import get_current_revision
39 39 _rev = get_current_revision()
40 40 except ImportError:
41 41 #this is needed when doing some setup.py operations
@@ -19,10 +19,10 b' def make_map(config):'
19 19 always_scan=config['debug'])
20 20 rmap.minimization = False
21 21 rmap.explicit = False
22
22
23 23 from rhodecode.lib.utils import is_valid_repo
24 24 from rhodecode.lib.utils import is_valid_repos_group
25
25
26 26 def check_repo(environ, match_dict):
27 27 """
28 28 check for valid repository for proper 404 handling
@@ -30,7 +30,7 b' def make_map(config):'
30 30 :param environ:
31 31 :param match_dict:
32 32 """
33
33
34 34 repo_name = match_dict.get('repo_name')
35 35 return is_valid_repo(repo_name, config['base_path'])
36 36
@@ -42,7 +42,7 b' def make_map(config):'
42 42 :param match_dict:
43 43 """
44 44 repos_group_name = match_dict.get('group_name')
45
45
46 46 return is_valid_repos_group(repos_group_name, config['base_path'])
47 47
48 48
@@ -333,13 +333,13 b' def make_map(config):'
333 333 # REPOSITORY ROUTES
334 334 #==========================================================================
335 335 rmap.connect('summary_home', '/{repo_name:.*}',
336 controller='summary',
336 controller='summary',
337 337 conditions=dict(function=check_repo))
338
339 # rmap.connect('repo_group_home', '/{group_name:.*}',
340 # controller='admin/repos_groups',action="show_by_name",
341 # conditions=dict(function=check_group))
342
338
339 rmap.connect('repos_group_home', '/{group_name:.*}',
340 controller='admin/repos_groups', action="show_by_name",
341 conditions=dict(function=check_group))
342
343 343 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
344 344 controller='changeset', revision='tip',
345 345 conditions=dict(function=check_repo))
@@ -64,20 +64,10 b' class ReposController(BaseController):'
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 c.repo_groups = Group.groups_choices()
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69
67 70 repo_model = RepoModel()
68
69 c.repo_groups = [('', '')]
70 parents_link = lambda k: h.literal('&raquo;'.join(
71 map(lambda k: k.group_name,
72 k.parents + [k])
73 )
74 )
75
76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 x in self.sa.query(Group).all()])
78 c.repo_groups = sorted(c.repo_groups,
79 key=lambda t: t[1].split('&raquo;')[0])
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 71 c.users_array = repo_model.get_users_js()
82 72 c.users_groups_array = repo_model.get_users_groups_js()
83 73
@@ -90,7 +80,7 b' class ReposController(BaseController):'
90 80 self.__load_defaults()
91 81
92 82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
93 repo = scm_repo = db_repo.scm_instance
83 repo = db_repo.scm_instance
94 84
95 85 if c.repo_info is None:
96 86 h.flash(_('%s repository is not mapped to db perhaps'
@@ -234,11 +224,11 b' class ReposController(BaseController):'
234 224 repo_groups=c.repo_groups_choices)()
235 225 try:
236 226 form_result = _form.to_python(dict(request.POST))
237 repo_model.update(repo_name, form_result)
227 repo = repo_model.update(repo_name, form_result)
238 228 invalidate_cache('get_repo_cached_%s' % repo_name)
239 229 h.flash(_('Repository %s updated successfully' % repo_name),
240 230 category='success')
241 changed_name = form_result['repo_name_full']
231 changed_name = repo.repo_name
242 232 action_logger(self.rhodecode_user, 'admin_updated_repo',
243 233 changed_name, '', self.sa)
244 234
@@ -9,9 +9,10 b' from pylons import request, response, se'
9 9 from pylons.controllers.util import abort, redirect
10 10 from pylons.i18n.translation import _
11 11
12 from sqlalchemy.exc import IntegrityError
13
12 14 from rhodecode.lib import helpers as h
13 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
14 HasPermissionAnyDecorator
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
15 16 from rhodecode.lib.base import BaseController, render
16 17 from rhodecode.model.db import Group
17 18 from rhodecode.model.repos_group import ReposGroupModel
@@ -31,19 +32,7 b' class ReposGroupsController(BaseControll'
31 32 super(ReposGroupsController, self).__before__()
32 33
33 34 def __load_defaults(self):
34
35 c.repo_groups = [('', '')]
36 parents_link = lambda k: h.literal('&raquo;'.join(
37 map(lambda k: k.group_name,
38 k.parents + [k])
39 )
40 )
41
42 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
43 x in self.sa.query(Group).all()])
44
45 c.repo_groups = sorted(c.repo_groups,
46 key=lambda t: t[1].split('&raquo;')[0])
35 c.repo_groups = Group.groups_choices()
47 36 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
48 37
49 38 def __load_data(self, group_id):
@@ -58,6 +47,8 b' class ReposGroupsController(BaseControll'
58 47
59 48 data = repo_group.get_dict()
60 49
50 data['group_name'] = repo_group.name
51
61 52 return data
62 53
63 54 @HasPermissionAnyDecorator('hg.admin')
@@ -169,13 +160,28 b' class ReposGroupsController(BaseControll'
169 160 repos_group_model.delete(id)
170 161 h.flash(_('removed repos group %s' % gr.group_name), category='success')
171 162 #TODO: in future action_logger(, '', '', '', self.sa)
163 except IntegrityError, e:
164 if e.message.find('groups_group_parent_id_fkey'):
165 log.error(traceback.format_exc())
166 h.flash(_('Cannot delete this group it still contains '
167 'subgroups'),
168 category='warning')
169 else:
170 log.error(traceback.format_exc())
171 h.flash(_('error occurred during deletion of repos '
172 'group %s' % gr.group_name), category='error')
173
172 174 except Exception:
173 175 log.error(traceback.format_exc())
174 h.flash(_('error occurred during deletion of repos group %s' % gr.group_name),
175 category='error')
176 h.flash(_('error occurred during deletion of repos '
177 'group %s' % gr.group_name), category='error')
176 178
177 179 return redirect(url('repos_groups'))
178 180
181 def show_by_name(self, group_name):
182 id_ = Group.get_by_group_name(group_name).group_id
183 return self.show(id_)
184
179 185 def show(self, id, format='html'):
180 186 """GET /repos_groups/id: Show a specific item"""
181 187 # url('repos_group', id=ID)
@@ -366,17 +366,7 b' class SettingsController(BaseController)'
366 366 def create_repository(self):
367 367 """GET /_admin/create_repository: Form to create a new item"""
368 368
369 c.repo_groups = [('', '')]
370 parents_link = lambda k: h.literal('&raquo;'.join(
371 map(lambda k: k.group_name,
372 k.parents + [k])
373 )
374 )
375
376 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
377 x in self.sa.query(Group).all()])
378 c.repo_groups = sorted(c.repo_groups,
379 key=lambda t: t[1].split('&raquo;')[0])
369 c.repo_groups = Group.groups_choices()
380 370 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
381 371
382 372 new_repo = request.GET.get('repo', '')
@@ -316,13 +316,6 b' class FilesController(BaseRepoController'
316 316 filename = file_obj.filename
317 317 content = file_obj.file
318 318
319 #TODO: REMOVE THIS !!
320 ################################
321 import ipdb;ipdb.set_trace()
322 print 'setting ipdb debuggin for rhodecode.controllers.files.FilesController.add'
323 ################################
324
325
326 319 node_path = os.path.join(location, filename)
327 320 author = self.rhodecode_user.full_contact
328 321
@@ -23,6 +23,8 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 import os
27
26 28 def __get_lem():
27 29 from pygments import lexers
28 30 from string import lower
@@ -379,3 +381,28 b' def get_changeset_safe(repo, rev):'
379 381 from rhodecode.lib.utils import EmptyChangeset
380 382 cs = EmptyChangeset(requested_revision=rev)
381 383 return cs
384
385
386 def get_current_revision(quiet=False):
387 """
388 Returns tuple of (number, id) from repository containing this package
389 or None if repository could not be found.
390
391 :param quiet: prints error for fetching revision if True
392 """
393
394 try:
395 from vcs import get_repo
396 from vcs.utils.helpers import get_scm
397 from vcs.exceptions import RepositoryError, VCSError
398 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
399 scm = get_scm(repopath)[0]
400 repo = get_repo(path=repopath, alias=scm)
401 tip = repo.get_changeset()
402 return (tip.revision, tip.short_id)
403 except (ImportError, RepositoryError, VCSError), err:
404 if not quiet:
405 print ("Cannot retrieve rhodecode's revision. Original error "
406 "was: %s" % err)
407 return None
408
@@ -94,11 +94,11 b' def __get_lockkey(func, *fargs, **fkwarg'
94 94 def locked_task(func):
95 95 def __wrapper(func, *fargs, **fkwargs):
96 96 lockkey = __get_lockkey(func, *fargs, **fkwargs)
97 lockkey_path = dn(dn(dn(os.path.abspath(__file__))))
97 lockkey_path = config['here']
98 98
99 99 log.info('running task with lockkey %s', lockkey)
100 100 try:
101 l = DaemonLock(jn(lockkey_path, lockkey))
101 l = DaemonLock(file_=jn(lockkey_path, lockkey))
102 102 ret = func(*fargs, **fkwargs)
103 103 l.release()
104 104 return ret
@@ -97,10 +97,11 b' def get_commits_stats(repo_name, ts_min_'
97 97
98 98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 99 ts_max_y)
100 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
100 lockkey_path = config['here']
101
101 102 log.info('running task with lockkey %s', lockkey)
102 103 try:
103 lock = l = DaemonLock(jn(lockkey_path, lockkey))
104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
104 105
105 106 #for js data compatibilty cleans the key for person from '
106 107 akc = lambda k: person(k).replace('"', "")
@@ -24,6 +24,9 b''
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 import os
28 from rhodecode import __platform__, PLATFORM_WIN
29
27 30 #==============================================================================
28 31 # json
29 32 #==============================================================================
@@ -358,3 +361,19 b' class OrderedDict(_odict, dict):'
358 361 # OrderedSet
359 362 #==============================================================================
360 363 from sqlalchemy.util import OrderedSet
364
365
366 #==============================================================================
367 # kill FUNCTIONS
368 #==============================================================================
369 if __platform__ in PLATFORM_WIN:
370 import ctypes
371
372 def kill(pid, sig):
373 """kill function for Win32"""
374 kernel32 = ctypes.windll.kernel32
375 handle = kernel32.OpenProcess(1, 0, pid)
376 return (0 != kernel32.TerminateProcess(handle, 0))
377
378 else:
379 kill = os.kill
@@ -598,12 +598,11 b' def repo_link(groups_and_repos):'
598 598 return repo_name
599 599 else:
600 600 def make_link(group):
601 return link_to(group.group_name, url('repos_group',
602 id=group.group_id))
601 return link_to(group.name, url('repos_group_home',
602 group_name=group.group_name))
603 603 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 604 " &raquo; " + repo_name)
605 605
606
607 606 def fancy_file_stats(stats):
608 607 """
609 608 Displays a fancy two colored bar for number of added/deleted
@@ -101,7 +101,7 b' class MakeIndex(BasePasterCommand):'
101 101 from rhodecode.lib.pidlock import LockHeld, DaemonLock
102 102 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
103 103 try:
104 l = DaemonLock(file=jn(dn(dn(index_location)), 'make_index.lock'))
104 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
105 105 WhooshIndexingDaemon(index_location=index_location,
106 106 repo_location=repo_location,
107 107 repo_list=repo_list)\
@@ -6,20 +6,7 b' import errno'
6 6 from warnings import warn
7 7 from multiprocessing.util import Finalize
8 8
9 from rhodecode import __platform__, PLATFORM_WIN
10
11 if __platform__ in PLATFORM_WIN:
12 import ctypes
13
14 def kill(pid, sig):
15 """kill function for Win32"""
16 kernel32 = ctypes.windll.kernel32
17 handle = kernel32.OpenProcess(1, 0, pid)
18 return (0 != kernel32.TerminateProcess(handle, 0))
19
20 else:
21 kill = os.kill
22
9 from rhodecode.lib.compat import kill
23 10
24 11 class LockHeld(Exception):
25 12 pass
@@ -29,17 +16,17 b' class DaemonLock(object):'
29 16 """daemon locking
30 17 USAGE:
31 18 try:
32 l = DaemonLock(desc='test lock')
19 l = DaemonLock(file_='/path/tolockfile',desc='test lock')
33 20 main()
34 21 l.release()
35 22 except LockHeld:
36 23 sys.exit(1)
37 24 """
38 25
39 def __init__(self, file=None, callbackfn=None,
26 def __init__(self, file_=None, callbackfn=None,
40 27 desc='daemon lock', debug=False):
41 28
42 self.pidfile = file if file else os.path.join(
29 self.pidfile = file_ if file_ else os.path.join(
43 30 os.path.dirname(__file__),
44 31 'running.lock')
45 32 self.callbackfn = callbackfn
@@ -360,16 +360,19 b' def map_groups(groups):'
360 360
361 361 parent = None
362 362 group = None
363 for lvl, group_name in enumerate(groups[:-1]):
363
364 # last element is repo in nested groups structure
365 groups = groups[:-1]
366
367 for lvl, group_name in enumerate(groups):
368 group_name = '/'.join(groups[:lvl] + [group_name])
364 369 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
365 370
366 371 if group is None:
367 372 group = Group(group_name, parent)
368 373 sa.add(group)
369 374 sa.commit()
370
371 375 parent = group
372
373 376 return group
374 377
375 378
@@ -386,8 +389,13 b' def repo2db_mapper(initial_repo_list, re'
386 389 rm = RepoModel()
387 390 user = sa.query(User).filter(User.admin == True).first()
388 391 added = []
392 # fixup groups paths to new format on the fly
393 # TODO: remove this in future
394 for g in Group.query().all():
395 g.group_name = g.get_new_name(g.name)
396 sa.add(g)
389 397 for name, repo in initial_repo_list.items():
390 group = map_groups(name.split(os.sep))
398 group = map_groups(name.split(Repository.url_sep()))
391 399 if not rm.get_by_repo_name(name, cache=False):
392 400 log.info('repository %s not found creating default', name)
393 401 added.append(name)
@@ -442,26 +450,6 b' def add_cache(settings):'
442 450 beaker.cache.cache_regions[region] = region_settings
443 451
444 452
445 def get_current_revision():
446 """Returns tuple of (number, id) from repository containing this package
447 or None if repository could not be found.
448 """
449
450 try:
451 from vcs import get_repo
452 from vcs.utils.helpers import get_scm
453 from vcs.exceptions import RepositoryError, VCSError
454 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
455 scm = get_scm(repopath)[0]
456 repo = get_repo(path=repopath, alias=scm)
457 tip = repo.get_changeset()
458 return (tip.revision, tip.short_id)
459 except (ImportError, RepositoryError, VCSError), err:
460 logging.debug("Cannot retrieve rhodecode's revision. Original error "
461 "was: %s" % err)
462 return None
463
464
465 453 #==============================================================================
466 454 # TEST FUNCTIONS AND CREATORS
467 455 #==============================================================================
@@ -483,7 +471,7 b' def create_test_index(repo_location, con'
483 471 os.makedirs(index_location)
484 472
485 473 try:
486 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
474 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
487 475 WhooshIndexingDaemon(index_location=index_location,
488 476 repo_location=repo_location)\
489 477 .run(full_index=full_index)
@@ -31,9 +31,10 b' from datetime import date'
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \
36 validates
35 37 from sqlalchemy.orm.interfaces import MapperExtension
36
37 38 from beaker.cache import cache_region, region_invalidate
38 39
39 40 from vcs import get_backend
@@ -42,13 +43,14 b' from vcs.exceptions import VCSError'
42 43 from vcs.utils.lazy import LazyProperty
43 44
44 45 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 generate_api_key
46 generate_api_key, safe_unicode
46 47 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 48 from rhodecode.lib.compat import json
48 49
49 50 from rhodecode.model.meta import Base, Session
50 51 from rhodecode.model.caching_query import FromCache
51 52
53
52 54 log = logging.getLogger(__name__)
53 55
54 56 #==============================================================================
@@ -126,7 +128,8 b' class BaseModel(object):'
126 128
127 129 @classmethod
128 130 def get(cls, id_):
129 return Session.query(cls).get(id_)
131 if id_:
132 return Session.query(cls).get(id_)
130 133
131 134 @classmethod
132 135 def delete(cls, id_):
@@ -140,12 +143,34 b' class RhodeCodeSettings(Base, BaseModel)'
140 143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 145 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 147
145 148 def __init__(self, k='', v=''):
146 149 self.app_settings_name = k
147 150 self.app_settings_value = v
148 151
152
153 @validates('_app_settings_value')
154 def validate_settings_value(self, key, val):
155 assert type(val) == unicode
156 return val
157
158 @hybrid_property
159 def app_settings_value(self):
160 v = self._app_settings_value
161 if v == 'ldap_active':
162 v = str2bool(v)
163 return v
164
165 @app_settings_value.setter
166 def app_settings_value(self,val):
167 """
168 Setter that will always make sure we use unicode in app_settings_value
169
170 :param val:
171 """
172 self._app_settings_value = safe_unicode(val)
173
149 174 def __repr__(self):
150 175 return "<%s('%s:%s')>" % (self.__class__.__name__,
151 176 self.app_settings_name, self.app_settings_value)
@@ -176,14 +201,11 b' class RhodeCodeSettings(Base, BaseModel)'
176 201 @classmethod
177 202 def get_ldap_settings(cls, cache=False):
178 203 ret = Session.query(cls)\
179 .filter(cls.app_settings_name.startswith('ldap_'))\
180 .all()
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
181 205 fd = {}
182 206 for row in ret:
183 207 fd.update({row.app_settings_name:row.app_settings_value})
184 208
185 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
186
187 209 return fd
188 210
189 211
@@ -283,7 +305,7 b' class User(Base, BaseModel):'
283 305 @classmethod
284 306 def get_by_username(cls, username, case_insensitive=False):
285 307 if case_insensitive:
286 return Session.query(cls).filter(cls.username.like(username)).scalar()
308 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
287 309 else:
288 310 return Session.query(cls).filter(cls.username == username).scalar()
289 311
@@ -486,12 +508,16 b' class Repository(Base, BaseModel):'
486 508 self.repo_id, self.repo_name)
487 509
488 510 @classmethod
511 def url_sep(cls):
512 return '/'
513
514 @classmethod
489 515 def get_by_repo_name(cls, repo_name):
490 516 q = Session.query(cls).filter(cls.repo_name == repo_name)
491 517
492 518 q = q.options(joinedload(Repository.fork))\
493 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.group))\
519 .options(joinedload(Repository.user))\
520 .options(joinedload(Repository.group))\
495 521
496 522 return q.one()
497 523
@@ -506,13 +532,14 b' class Repository(Base, BaseModel):'
506 532
507 533 :param cls:
508 534 """
509 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
535 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
536 cls.url_sep())
510 537 q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 538 return q.one().ui_value
512 539
513 540 @property
514 541 def just_name(self):
515 return self.repo_name.split(os.sep)[-1]
542 return self.repo_name.split(Repository.url_sep())[-1]
516 543
517 544 @property
518 545 def groups_with_parents(self):
@@ -541,7 +568,8 b' class Repository(Base, BaseModel):'
541 568 Returns base full path for that repository means where it actually
542 569 exists on a filesystem
543 570 """
544 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
571 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
572 Repository.url_sep())
545 573 q.options(FromCache("sql_cache_short", "repository_repo_path"))
546 574 return q.one().ui_value
547 575
@@ -551,9 +579,18 b' class Repository(Base, BaseModel):'
551 579 # we need to split the name by / since this is how we store the
552 580 # names in the database, but that eventually needs to be converted
553 581 # into a valid system path
554 p += self.repo_name.split('/')
582 p += self.repo_name.split(Repository.url_sep())
555 583 return os.path.join(*p)
556 584
585 def get_new_name(self, repo_name):
586 """
587 returns new full repository name based on assigned group and new new
588
589 :param group_name:
590 """
591 path_prefix = self.group.full_path_splitted if self.group else []
592 return Repository.url_sep().join(path_prefix + [repo_name])
593
557 594 @property
558 595 def _ui(self):
559 596 """
@@ -718,9 +755,26 b' class Group(Base, BaseModel):'
718 755 self.group_name)
719 756
720 757 @classmethod
758 def groups_choices(cls):
759 from webhelpers.html import literal as _literal
760 repo_groups = [('', '')]
761 sep = ' &raquo; '
762 _name = lambda k: _literal(sep.join(k))
763
764 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
765 for x in cls.query().all()])
766
767 repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0])
768 return repo_groups
769
770 @classmethod
721 771 def url_sep(cls):
722 772 return '/'
723 773
774 @classmethod
775 def get_by_group_name(cls, group_name):
776 return cls.query().filter(cls.group_name == group_name).scalar()
777
724 778 @property
725 779 def parents(self):
726 780 parents_recursion_limit = 5
@@ -750,9 +804,16 b' class Group(Base, BaseModel):'
750 804 return Session.query(Group).filter(Group.parent_group == self)
751 805
752 806 @property
807 def name(self):
808 return self.group_name.split(Group.url_sep())[-1]
809
810 @property
753 811 def full_path(self):
754 return Group.url_sep().join([g.group_name for g in self.parents] +
755 [self.group_name])
812 return self.group_name
813
814 @property
815 def full_path_splitted(self):
816 return self.group_name.split(Group.url_sep())
756 817
757 818 @property
758 819 def repositories(self):
@@ -771,6 +832,17 b' class Group(Base, BaseModel):'
771 832
772 833 return cnt + children_count(self)
773 834
835
836 def get_new_name(self, group_name):
837 """
838 returns new full group name based on parent and new name
839
840 :param group_name:
841 """
842 path_prefix = self.parent_group.full_path_splitted if self.parent_group else []
843 return Group.url_sep().join(path_prefix + [group_name])
844
845
774 846 class Permission(Base, BaseModel):
775 847 __tablename__ = 'permissions'
776 848 __table_args__ = {'extend_existing':True}
@@ -122,7 +122,7 b' def ValidReposGroup(edit, old_data):'
122 122 def validate_python(self, value, state):
123 123 #TODO WRITE VALIDATIONS
124 124 group_name = value.get('group_name')
125 group_parent_id = int(value.get('group_parent_id') or - 1)
125 group_parent_id = int(value.get('group_parent_id') or -1)
126 126
127 127 # slugify repo group just in case :)
128 128 slug = repo_name_slug(group_name)
@@ -250,7 +250,7 b' def ValidRepoName(edit, old_data):'
250 250 gr = Group.get(value.get('repo_group'))
251 251 group_path = gr.full_path
252 252 # value needs to be aware of group name in order to check
253 # db key This is an actuall just the name to store in the
253 # db key This is an actual just the name to store in the
254 254 # database
255 255 repo_name_full = group_path + Group.url_sep() + repo_name
256 256 else:
@@ -259,25 +259,32 b' def ValidRepoName(edit, old_data):'
259 259
260 260
261 261 value['repo_name_full'] = repo_name_full
262 if old_data.get('repo_name') != repo_name_full or not edit:
262 rename = old_data.get('repo_name') != repo_name_full
263 create = not edit
264 if rename or create:
263 265
264 266 if group_path != '':
265 267 if RepoModel().get_by_repo_name(repo_name_full,):
266 268 e_dict = {'repo_name':_('This repository already '
267 'exists in group "%s"') %
269 'exists in a group "%s"') %
268 270 gr.group_name}
269 271 raise formencode.Invalid('', value, state,
270 272 error_dict=e_dict)
273 elif Group.get_by_group_name(repo_name_full):
274 e_dict = {'repo_name':_('There is a group with this'
275 ' name already "%s"') %
276 repo_name_full}
277 raise formencode.Invalid('', value, state,
278 error_dict=e_dict)
271 279
272 else:
273 if RepoModel().get_by_repo_name(repo_name_full):
280 elif RepoModel().get_by_repo_name(repo_name_full):
274 281 e_dict = {'repo_name':_('This repository '
275 282 'already exists')}
276 283 raise formencode.Invalid('', value, state,
277 284 error_dict=e_dict)
285
278 286 return value
279 287
280
281 288 return _ValidRepoName
282 289
283 290 def ValidForkName():
@@ -98,7 +98,7 b' class RepoModel(BaseModel):'
98 98 try:
99 99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100 100
101 #update permissions
101 # update permissions
102 102 for member, perm, member_type in form_data['perms_updates']:
103 103 if member_type == 'user':
104 104 r2p = self.sa.query(RepoToPerm)\
@@ -122,7 +122,7 b' class RepoModel(BaseModel):'
122 122 perm).scalar()
123 123 self.sa.add(g2p)
124 124
125 #set new permissions
125 # set new permissions
126 126 for member, perm, member_type in form_data['perms_new']:
127 127 if member_type == 'user':
128 128 r2p = RepoToPerm()
@@ -144,26 +144,29 b' class RepoModel(BaseModel):'
144 144 .scalar()
145 145 self.sa.add(g2p)
146 146
147 #update current repo
147 # update current repo
148 148 for k, v in form_data.items():
149 149 if k == 'user':
150 150 cur_repo.user = User.get_by_username(v)
151 151 elif k == 'repo_name':
152 cur_repo.repo_name = form_data['repo_name_full']
152 pass
153 153 elif k == 'repo_group':
154 154 cur_repo.group_id = v
155 155
156 156 else:
157 157 setattr(cur_repo, k, v)
158 158
159 new_name = cur_repo.get_new_name(form_data['repo_name'])
160 cur_repo.repo_name = new_name
161
159 162 self.sa.add(cur_repo)
160 163
161 if repo_name != form_data['repo_name_full']:
164 if repo_name != new_name:
162 165 # rename repository
163 self.__rename_repo(old=repo_name,
164 new=form_data['repo_name_full'])
166 self.__rename_repo(old=repo_name, new=new_name)
165 167
166 168 self.sa.commit()
169 return cur_repo
167 170 except:
168 171 log.error(traceback.format_exc())
169 172 self.sa.rollback()
@@ -235,7 +238,7 b' class RepoModel(BaseModel):'
235 238 from rhodecode.model.scm import ScmModel
236 239 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
237 240 cur_user.user_id)
238
241 return new_repo
239 242 except:
240 243 log.error(traceback.format_exc())
241 244 self.sa.rollback()
@@ -302,7 +305,7 b' class RepoModel(BaseModel):'
302 305 :param parent_id:
303 306 :param clone_uri:
304 307 """
305 from rhodecode.lib.utils import is_valid_repo
308 from rhodecode.lib.utils import is_valid_repo,is_valid_repos_group
306 309
307 310 if new_parent_id:
308 311 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
@@ -313,12 +316,20 b' class RepoModel(BaseModel):'
313 316 repo_path = os.path.join(*map(lambda x:safe_str(x),
314 317 [self.repos_path, new_parent_path, repo_name]))
315 318
316 if is_valid_repo(repo_path, self.repos_path) is False:
317 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
318 clone_uri)
319 backend = get_backend(alias)
319
320 # check if this path is not a repository
321 if is_valid_repo(repo_path, self.repos_path):
322 raise Exception('This path %s is a valid repository' % repo_path)
320 323
321 backend(repo_path, create=True, src_url=clone_uri)
324 # check if this path is a group
325 if is_valid_repos_group(repo_path, self.repos_path):
326 raise Exception('This path %s is a valid group' % repo_path)
327
328 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
329 clone_uri)
330 backend = get_backend(alias)
331
332 backend(repo_path, create=True, src_url=clone_uri)
322 333
323 334
324 335 def __rename_repo(self, old, new):
@@ -50,7 +50,7 b' class ReposGroupModel(BaseModel):'
50 50 q = RhodeCodeUi.get_by_key('/').one()
51 51 return q.ui_value
52 52
53 def __create_group(self, group_name, parent_id):
53 def __create_group(self, group_name):
54 54 """
55 55 makes repositories group on filesystem
56 56
@@ -58,44 +58,30 b' class ReposGroupModel(BaseModel):'
58 58 :param parent_id:
59 59 """
60 60
61 if parent_id:
62 paths = Group.get(parent_id).full_path.split(Group.url_sep())
63 parent_path = os.sep.join(paths)
64 else:
65 parent_path = ''
66
67 create_path = os.path.join(self.repos_path, parent_path, group_name)
61 create_path = os.path.join(self.repos_path, group_name)
68 62 log.debug('creating new group in %s', create_path)
69 63
70 64 if os.path.isdir(create_path):
71 65 raise Exception('That directory already exists !')
72 66
73
74 67 os.makedirs(create_path)
75 68
76
77 def __rename_group(self, old, old_parent_id, new, new_parent_id):
69 def __rename_group(self, old, new):
78 70 """
79 71 Renames a group on filesystem
80 72
81 73 :param group_name:
82 74 """
75
76 if old == new:
77 log.debug('skipping group rename')
78 return
79
83 80 log.debug('renaming repos group from %s to %s', old, new)
84 81
85 if new_parent_id:
86 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
87 new_parent_path = os.sep.join(paths)
88 else:
89 new_parent_path = ''
90 82
91 if old_parent_id:
92 paths = Group.get(old_parent_id).full_path.split(Group.url_sep())
93 old_parent_path = os.sep.join(paths)
94 else:
95 old_parent_path = ''
96
97 old_path = os.path.join(self.repos_path, old_parent_path, old)
98 new_path = os.path.join(self.repos_path, new_parent_path, new)
83 old_path = os.path.join(self.repos_path, old)
84 new_path = os.path.join(self.repos_path, new)
99 85
100 86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
101 87
@@ -114,22 +100,23 b' class ReposGroupModel(BaseModel):'
114 100 paths = os.sep.join(paths)
115 101
116 102 rm_path = os.path.join(self.repos_path, paths)
117 os.rmdir(rm_path)
103 if os.path.isdir(rm_path):
104 # delete only if that path really exists
105 os.rmdir(rm_path)
118 106
119 107 def create(self, form_data):
120 108 try:
121 109 new_repos_group = Group()
122 new_repos_group.group_name = form_data['group_name']
123 new_repos_group.group_description = \
124 form_data['group_description']
125 new_repos_group.group_parent_id = form_data['group_parent_id']
110 new_repos_group.group_description = form_data['group_description']
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
126 113
127 114 self.sa.add(new_repos_group)
128 115
129 self.__create_group(form_data['group_name'],
130 form_data['group_parent_id'])
116 self.__create_group(new_repos_group.group_name)
131 117
132 118 self.sa.commit()
119 return new_repos_group
133 120 except:
134 121 log.error(traceback.format_exc())
135 122 self.sa.rollback()
@@ -139,23 +126,27 b' class ReposGroupModel(BaseModel):'
139 126
140 127 try:
141 128 repos_group = Group.get(repos_group_id)
142 old_name = repos_group.group_name
143 old_parent_id = repos_group.group_parent_id
129 old_path = repos_group.full_path
144 130
145 repos_group.group_name = form_data['group_name']
146 repos_group.group_description = \
147 form_data['group_description']
148 repos_group.group_parent_id = form_data['group_parent_id']
131 #change properties
132 repos_group.group_description = form_data['group_description']
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
134 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
135
136 new_path = repos_group.full_path
149 137
150 138 self.sa.add(repos_group)
151 139
152 if old_name != form_data['group_name'] or (old_parent_id !=
153 form_data['group_parent_id']):
154 self.__rename_group(old=old_name, old_parent_id=old_parent_id,
155 new=form_data['group_name'],
156 new_parent_id=form_data['group_parent_id'])
140 self.__rename_group(old_path, new_path)
141
142 # we need to get all repositories from this new group and
143 # rename them accordingly to new group path
144 for r in repos_group.repositories:
145 r.repo_name = r.get_new_name(r.just_name)
146 self.sa.add(r)
157 147
158 148 self.sa.commit()
149 return repos_group
159 150 except:
160 151 log.error(traceback.format_exc())
161 152 self.sa.rollback()
@@ -22,6 +22,7 b''
22 22 #
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 import os
25 26 import time
26 27 import traceback
27 28 import logging
@@ -146,10 +147,15 b' class ScmModel(BaseModel):'
146 147 repos_list = {}
147 148
148 149 for name, path in get_filesystem_repos(repos_path, recursive=True):
150
151 # name need to be decomposed and put back together using the /
152 # since this is internal storage separator for rhodecode
153 name = Repository.url_sep().join(name.split(os.sep))
154
149 155 try:
150 156 if name in repos_list:
151 157 raise RepositoryError('Duplicate repository name %s '
152 'found in %s' % (name, path))
158 'found in %s' % (name, path))
153 159 else:
154 160
155 161 klass = get_backend(path[0])
@@ -246,12 +246,13 b' color:#FFF;'
246 246 }
247 247
248 248 #header #header-inner {
249 height:40px;
249 min-height:40px;
250 250 clear:both;
251 251 position:relative;
252 252 background:#003367 url("../images/header_inner.png") repeat-x;
253 253 margin:0;
254 254 padding:0;
255 display:block;
255 256 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
256 257 -webkit-border-radius: 4px 4px 4px 4px;
257 258 -khtml-border-radius: 4px 4px 4px 4px;
@@ -272,7 +273,10 b' padding:0;'
272 273 #header #header-inner #home a:hover {
273 274 background-position:0 -40px;
274 275 }
275
276 #header #header-inner #logo {
277 float: left;
278 position: absolute;
279 }
276 280 #header #header-inner #logo h1 {
277 281 color:#FFF;
278 282 font-size:18px;
@@ -348,7 +352,7 b' top:0;'
348 352 left:0;
349 353 border-left:none;
350 354 border-right:1px solid #2e5c89;
351 padding:8px 8px 4px;
355 padding:8px 6px 4px;
352 356 }
353 357
354 358 #header #header-inner #quick li span.icon_short {
@@ -356,7 +360,10 b' top:0;'
356 360 left:0;
357 361 border-left:none;
358 362 border-right:1px solid #2e5c89;
359 padding:9px 4px 4px;
363 padding:8px 6px 4px;
364 }
365 #header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img {
366 margin: 0px -2px 0px 0px;
360 367 }
361 368
362 369 #header #header-inner #quick li a:hover {
@@ -564,6 +571,14 b' padding:12px 9px 7px 24px;'
564 571 }
565 572
566 573
574 .groups_breadcrumbs a {
575 color: #fff;
576 }
577 .groups_breadcrumbs a:hover {
578 color: #bfe3ff;
579 text-decoration: none;
580 }
581
567 582 .quick_repo_menu{
568 583 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
569 584 cursor: pointer;
@@ -1895,19 +1910,6 b' div.browserblock .add_node{'
1895 1910 padding-left: 5px;
1896 1911 }
1897 1912
1898 div.browserblock .search_activate #filter_activate,div.browserblock .add_node a{
1899 vertical-align: sub;
1900 border: 1px solid;
1901 padding:2px;
1902 -webkit-border-radius: 4px 4px 4px 4px;
1903 -khtml-border-radius: 4px 4px 4px 4px;
1904 -moz-border-radius: 4px 4px 4px 4px;
1905 border-radius: 4px 4px 4px 4px;
1906 background: url("../images/button.png") repeat-x scroll 0 0 #E5E3E3;
1907 border-color: #DDDDDD #DDDDDD #C6C6C6 #C6C6C6;
1908 color: #515151;
1909 }
1910
1911 1913 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1912 1914 text-decoration: none !important;
1913 1915 }
@@ -2371,8 +2373,10 b' border-left:1px solid #316293;'
2371 2373 border:1px solid #316293;
2372 2374 }
2373 2375
2374
2375 input.ui-button-small {
2376 .ui-button-small a:hover {
2377
2378 }
2379 input.ui-button-small,.ui-button-small {
2376 2380 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2377 2381 border-top:1px solid #DDD !important;
2378 2382 border-left:1px solid #c6c6c6 !important;
@@ -2387,17 +2391,19 b' margin:0 !important;'
2387 2391 border-radius: 4px 4px 4px 4px !important;
2388 2392 box-shadow: 0 1px 0 #ececec !important;
2389 2393 cursor: pointer !important;
2390 }
2391
2392 input.ui-button-small:hover {
2394 padding:0px 2px 1px 2px;
2395 }
2396
2397 input.ui-button-small:hover,.ui-button-small:hover {
2393 2398 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2394 2399 border-top:1px solid #ccc !important;
2395 2400 border-left:1px solid #bebebe !important;
2396 2401 border-right:1px solid #b1b1b1 !important;
2397 border-bottom:1px solid #afafaf !important;
2398 }
2399
2400 input.ui-button-small-blue {
2402 border-bottom:1px solid #afafaf !important;
2403 text-decoration: none;
2404 }
2405
2406 input.ui-button-small-blue,.ui-button-small-blue {
2401 2407 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2402 2408 border-top:1px solid #5c91a4;
2403 2409 border-left:1px solid #2a6f89;
@@ -2410,6 +2416,7 b' color:#fff;'
2410 2416 border-radius: 4px 4px 4px 4px;
2411 2417 box-shadow: 0 1px 0 #ececec;
2412 2418 cursor: pointer;
2419 padding:0px 2px 1px 2px;
2413 2420 }
2414 2421
2415 2422 input.ui-button-small-blue:hover {
@@ -5,12 +5,14 b''
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs()">
8 <span class="groups_breadcrumbs">
8 9 ${_('Groups')}
9 10 %if c.group.parent_group:
10 &raquo; ${h.link_to(c.group.parent_group.group_name,
11 h.url('repos_group',id=c.group.parent_group.group_id))}
11 &raquo; ${h.link_to(c.group.parent_group.name,
12 h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
12 13 %endif
13 &raquo; "${c.group.group_name}" ${_('with')}
14 &raquo; "${c.group.name}" ${_('with')}
15 </span>
14 16 </%def>
15 17
16 18 <%def name="page_nav()">
@@ -2,14 +2,14 b''
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.group_name} - ${c.rhodecode_name}
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.group_name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
@@ -44,14 +44,14 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.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
47 ${h.link_to(h.literal(' &raquo; '.join([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>
51 51 <td><b>${gr.repositories.count()}</b></td>
52 52 <td>
53 53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
54 ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
54 ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
55 55 ${h.end_form()}
56 56 </td>
57 57 </tr>
@@ -11,9 +11,9 b''
11 11 ${h.form(h.url.current())}
12 12 <div class="info_box">
13 13 <span class="rev">${_('view')}@rev</span>
14 <a class="rev" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
14 <a class="ui-button-small" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
15 15 ${h.text('at_rev',value=c.changeset.revision,size=5)}
16 <a class="rev" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
16 <a class="ui-button-small" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
17 17 ## ${h.submit('view',_('view'),class_="ui-button-small")}
18 18 </div>
19 19 ${h.end_form()}
@@ -24,11 +24,11 b''
24 24 </div>
25 25 <div class="browser-search">
26 26 <div id="search_activate_id" class="search_activate">
27 <a id="filter_activate" href="#">${_('search file list')}</a>
27 <a class="ui-button-small" id="filter_activate" href="#">${_('search file list')}</a>
28 28 </div>
29 29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 <div id="add_node_id" class="add_node">
31 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
30 <div id="add_node_id" class="add_node">
31 <a class="ui-button-small" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
32 32 </div>
33 33 % endif
34 34 <div>
@@ -35,7 +35,7 b''
35 35 <td>
36 36 <div style="white-space: nowrap">
37 37 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
38 ${h.link_to(gr.group_name,url('repos_group',id=gr.group_id))}
38 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
39 39 </div>
40 40 </td>
41 41 <td>${gr.group_description}</td>
@@ -45,17 +45,17 b''
45 45
46 46 ##REPO TYPE
47 47 %if c.dbrepo.repo_type =='hg':
48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
49 49 %endif
50 50 %if c.dbrepo.repo_type =='git':
51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
52 52 %endif
53 53
54 54 ##PUBLIC/PRIVATE
55 55 %if c.dbrepo.private:
56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
57 57 %else:
58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
59 59 %endif
60 60
61 61 ##REPO NAME
@@ -67,7 +67,7 b''
67 67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 68 <img class="icon" alt="${_('public')}"
69 69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
70 src="${h.url("/images/icons/arrow_divide.png")}"/>
70 src="${h.url('/images/icons/arrow_divide.png')}"/>
71 71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 72 </a>
73 73 </div>
@@ -78,7 +78,7 b''
78 78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 79 <img class="icon" alt="${_('remote clone')}"
80 80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
81 src="${h.url("/images/icons/connect.png")}"/>
81 src="${h.url('/images/icons/connect.png')}"/>
82 82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 83 </a>
84 84 </div>
@@ -151,7 +151,7 b''
151 151 %elif c.enable_downloads is False:
152 152 ${_('Downloads are disabled for this repository')}
153 153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
154 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
154 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
155 155 %endif
156 156 %else:
157 157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
@@ -317,7 +317,7 b''
317 317 %if c.no_data:
318 318 ${c.no_data_msg}
319 319 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
320 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
320 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
321 321 %endif
322 322
323 323 %else:
@@ -13,7 +13,7 b' class TestChangelogController(TestContro'
13 13 """name="5e204e7583b9" type="checkbox" value="1" />"""
14 14 in response.body)
15 15 self.assertTrue("""<span>commit 154: 5e204e7583b9@2010-08-10 """
16 """01:18:46</span>""" in response.body)
16 """02:18:46</span>""" in response.body)
17 17 self.assertTrue("""Small update at simplevcs app""" in response.body)
18 18
19 19
@@ -43,7 +43,7 b' class TestChangelogController(TestContro'
43 43 """name="46ad32a4f974" type="checkbox" value="1" />"""
44 44 in response.body)
45 45 self.assertTrue("""<span>commit 64: 46ad32a4f974@2010-04-20"""
46 """ 00:33:21</span>"""in response.body)
46 """ 01:33:21</span>"""in response.body)
47 47
48 48 self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
49 49 """579b1" class="changed_total tooltip" """
@@ -145,8 +145,8 b' class TestLoginController(TestController'
145 145 'lastname':'test'})
146 146
147 147 self.assertEqual(response.status , '200 OK')
148 assert 'An email address must contain a single @' in response.body
149 assert 'This username already exists' in response.body
148 self.assertTrue('An email address must contain a single @' in response.body)
149 self.assertTrue('This username already exists' in response.body)
150 150
151 151
152 152
@@ -160,7 +160,7 b' class TestLoginController(TestController'
160 160 'lastname':'test'})
161 161
162 162 self.assertEqual(response.status , '200 OK')
163 assert 'Invalid characters in password' in response.body
163 self.assertTrue('Invalid characters in password' in response.body)
164 164
165 165
166 166 def test_register_password_mismatch(self):
@@ -1,2 +1,153 b''
1 import os
1 2 import unittest
2 3 from rhodecode.tests import *
4
5 from rhodecode.model.repos_group import ReposGroupModel
6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import Group, User
8 from sqlalchemy.exc import IntegrityError
9
10 class TestReposGroups(unittest.TestCase):
11
12 def setUp(self):
13 self.g1 = self.__make_group('test1', skip_if_exists=True)
14 self.g2 = self.__make_group('test2', skip_if_exists=True)
15 self.g3 = self.__make_group('test3', skip_if_exists=True)
16
17 def tearDown(self):
18 print 'out'
19
20 def __check_path(self, *path):
21 path = [TESTS_TMP_PATH] + list(path)
22 path = os.path.join(*path)
23 return os.path.isdir(path)
24
25 def _check_folders(self):
26 print os.listdir(TESTS_TMP_PATH)
27
28 def __make_group(self, path, desc='desc', parent_id=None,
29 skip_if_exists=False):
30
31 gr = Group.get_by_group_name(path)
32 if gr and skip_if_exists:
33 return gr
34
35 form_data = dict(group_name=path,
36 group_description=desc,
37 group_parent_id=parent_id)
38 gr = ReposGroupModel().create(form_data)
39 return gr
40
41 def __delete_group(self, id_):
42 ReposGroupModel().delete(id_)
43
44
45 def __update_group(self, id_, path, desc='desc', parent_id=None):
46 form_data = dict(group_name=path,
47 group_description=desc,
48 group_parent_id=parent_id)
49
50 gr = ReposGroupModel().update(id_, form_data)
51 return gr
52
53 def test_create_group(self):
54 g = self.__make_group('newGroup')
55 self.assertEqual(g.full_path, 'newGroup')
56
57 self.assertTrue(self.__check_path('newGroup'))
58
59
60 def test_create_same_name_group(self):
61 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
62
63
64 def test_same_subgroup(self):
65 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
66 self.assertEqual(sg1.parent_group, self.g1)
67 self.assertEqual(sg1.full_path, 'test1/sub1')
68 self.assertTrue(self.__check_path('test1', 'sub1'))
69
70 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
71 self.assertEqual(ssg1.parent_group, sg1)
72 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
73 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
74
75
76 def test_remove_group(self):
77 sg1 = self.__make_group('deleteme')
78 self.__delete_group(sg1.group_id)
79
80 self.assertEqual(Group.get(sg1.group_id), None)
81 self.assertFalse(self.__check_path('deteteme'))
82
83 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
84 self.__delete_group(sg1.group_id)
85
86 self.assertEqual(Group.get(sg1.group_id), None)
87 self.assertFalse(self.__check_path('test1', 'deteteme'))
88
89
90 def test_rename_single_group(self):
91 sg1 = self.__make_group('initial')
92
93 new_sg1 = self.__update_group(sg1.group_id, 'after')
94 self.assertTrue(self.__check_path('after'))
95 self.assertEqual(Group.get_by_group_name('initial'), None)
96
97
98 def test_update_group_parent(self):
99
100 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
101
102 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
103 self.assertTrue(self.__check_path('test1', 'after'))
104 self.assertEqual(Group.get_by_group_name('test1/initial'), None)
105
106
107 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
108 self.assertTrue(self.__check_path('test3', 'after'))
109 self.assertEqual(Group.get_by_group_name('test3/initial'), None)
110
111
112 new_sg1 = self.__update_group(sg1.group_id, 'hello')
113 self.assertTrue(self.__check_path('hello'))
114
115 self.assertEqual(Group.get_by_group_name('hello'), new_sg1)
116
117
118
119 def test_subgrouping_with_repo(self):
120
121 g1 = self.__make_group('g1')
122 g2 = self.__make_group('g2')
123
124 # create new repo
125 form_data = dict(repo_name='john',
126 repo_name_full='john',
127 fork_name=None,
128 description=None,
129 repo_group=None,
130 private=False,
131 repo_type='hg',
132 clone_uri=None)
133 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
134 r = RepoModel().create(form_data, cur_user)
135
136 self.assertEqual(r.repo_name, 'john')
137
138 # put repo into group
139 form_data = form_data
140 form_data['repo_group'] = g1.group_id
141 form_data['perms_new'] = []
142 form_data['perms_updates'] = []
143 RepoModel().update(r.repo_name, form_data)
144 self.assertEqual(r.repo_name, 'g1/john')
145
146
147 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
148 self.assertTrue(self.__check_path('g2', 'g1'))
149
150 # test repo
151 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
152
153
@@ -11,30 +11,32 b' if py_version < (2, 5):'
11 11
12 12 requirements = [
13 13 "Pylons==1.0.0",
14 "Beaker==1.5.4",
14 15 "WebHelpers>=1.2",
15 16 "formencode==1.2.4",
16 "SQLAlchemy>=0.7.2,<0.8",
17 "Mako>=0.4.2",
17 "SQLAlchemy==0.7.3",
18 "Mako==0.5.0",
18 19 "pygments>=1.4",
19 "mercurial>=1.9,<2.0",
20 "mercurial>=1.9.3,<2.0",
20 21 "whoosh<1.8",
21 22 "celery>=2.2.5,<2.3",
22 23 "babel",
23 24 "python-dateutil>=1.5.0,<2.0.0",
24 "dulwich>=0.8.0",
25 "vcs>=0.2.1.dev",
25 "dulwich>=0.8.0,<0.9.0",
26 "vcs>=0.2.3.dev",
26 27 "webob==1.0.8"
27 28 ]
28 29
29 30 dependency_links = [
30 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.2.dev",
31 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.2.dev",
31 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.3.dev",
32 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.3.dev",
32 33 ]
33 34
34 35 classifiers = ['Development Status :: 4 - Beta',
35 36 'Environment :: Web Environment',
36 37 'Framework :: Pylons',
37 38 'Intended Audience :: Developers',
39 'License :: OSI Approved :: GNU General Public License (GPL)',
38 40 'Operating System :: OS Independent',
39 41 'Programming Language :: Python',
40 42 'Programming Language :: Python :: 2.5',
General Comments 0
You need to be logged in to leave comments. Login now