##// 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 Jason Harris <jason@jasonfharris.com>
4 Jason Harris <jason@jasonfharris.com>
5 Thayne Harbaugh <thayne@fusionio.com>
5 Thayne Harbaugh <thayne@fusionio.com>
6 cejones
6 cejones
7 Thomas Waldmann <tw-public@gmx.de>
7 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Dmitri Kuznetsov
9 Dmitri Kuznetsov
9 Jared Bunting <jared.bunting@peachjean.com>
10 Jared Bunting <jared.bunting@peachjean.com>
@@ -11,4 +12,4 b' List of contributors to RhodeCode projec'
11 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 Ankit Solanki <ankit.solanki@gmail.com>
13 Ankit Solanki <ankit.solanki@gmail.com>
13 Liad Shani <liadff@gmail.com>
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 news
13 news
14 ----
14 ----
15
15
16
16 fixes
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 1.2.1 (**2011-10-08**)
44 1.2.1 (**2011-10-08**)
20 ======================
45 ======================
21
46
@@ -443,8 +443,8 b' in the production.ini file::'
443 In order to not have the statics served by the application. This improves speed.
443 In order to not have the statics served by the application. This improves speed.
444
444
445
445
446 Apache virtual host example
446 Apache virtual host reverse proxy example
447 ---------------------------
447 -----------------------------------------
448
448
449 Here is a sample configuration file for apache using proxy::
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 Apache's WSGI config
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 Example wsgi dispatch script::
532 Example wsgi dispatch script::
508
533
@@ -512,6 +537,9 b' Example wsgi dispatch script::'
512
537
513 # sometimes it's needed to set the curent dir
538 # sometimes it's needed to set the curent dir
514 os.chdir('/home/web/rhodecode/')
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 from paste.deploy import loadapp
544 from paste.deploy import loadapp
517 from paste.script.util.logging_config import fileConfig
545 from paste.script.util.logging_config import fileConfig
@@ -519,6 +547,10 b' Example wsgi dispatch script::'
519 fileConfig('/home/web/rhodecode/production.ini')
547 fileConfig('/home/web/rhodecode/production.ini')
520 application = loadapp('config:/home/web/rhodecode/production.ini')
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 Other configuration files
555 Other configuration files
524 -------------------------
556 -------------------------
@@ -35,7 +35,7 b" PLATFORM_WIN = ('Windows')"
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
36
36
37 try:
37 try:
38 from rhodecode.lib.utils import get_current_revision
38 from rhodecode.lib import get_current_revision
39 _rev = get_current_revision()
39 _rev = get_current_revision()
40 except ImportError:
40 except ImportError:
41 #this is needed when doing some setup.py operations
41 #this is needed when doing some setup.py operations
@@ -19,10 +19,10 b' def make_map(config):'
19 always_scan=config['debug'])
19 always_scan=config['debug'])
20 rmap.minimization = False
20 rmap.minimization = False
21 rmap.explicit = False
21 rmap.explicit = False
22
22
23 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repo
24 from rhodecode.lib.utils import is_valid_repos_group
24 from rhodecode.lib.utils import is_valid_repos_group
25
25
26 def check_repo(environ, match_dict):
26 def check_repo(environ, match_dict):
27 """
27 """
28 check for valid repository for proper 404 handling
28 check for valid repository for proper 404 handling
@@ -30,7 +30,7 b' def make_map(config):'
30 :param environ:
30 :param environ:
31 :param match_dict:
31 :param match_dict:
32 """
32 """
33
33
34 repo_name = match_dict.get('repo_name')
34 repo_name = match_dict.get('repo_name')
35 return is_valid_repo(repo_name, config['base_path'])
35 return is_valid_repo(repo_name, config['base_path'])
36
36
@@ -42,7 +42,7 b' def make_map(config):'
42 :param match_dict:
42 :param match_dict:
43 """
43 """
44 repos_group_name = match_dict.get('group_name')
44 repos_group_name = match_dict.get('group_name')
45
45
46 return is_valid_repos_group(repos_group_name, config['base_path'])
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 # REPOSITORY ROUTES
333 # REPOSITORY ROUTES
334 #==========================================================================
334 #==========================================================================
335 rmap.connect('summary_home', '/{repo_name:.*}',
335 rmap.connect('summary_home', '/{repo_name:.*}',
336 controller='summary',
336 controller='summary',
337 conditions=dict(function=check_repo))
337 conditions=dict(function=check_repo))
338
338
339 # rmap.connect('repo_group_home', '/{group_name:.*}',
339 rmap.connect('repos_group_home', '/{group_name:.*}',
340 # controller='admin/repos_groups',action="show_by_name",
340 controller='admin/repos_groups', action="show_by_name",
341 # conditions=dict(function=check_group))
341 conditions=dict(function=check_group))
342
342
343 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
343 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
344 controller='changeset', revision='tip',
344 controller='changeset', revision='tip',
345 conditions=dict(function=check_repo))
345 conditions=dict(function=check_repo))
@@ -64,20 +64,10 b' class ReposController(BaseController):'
64 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
65
65
66 def __load_defaults(self):
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 repo_model = RepoModel()
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 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
82 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
83
73
@@ -90,7 +80,7 b' class ReposController(BaseController):'
90 self.__load_defaults()
80 self.__load_defaults()
91
81
92 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
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 if c.repo_info is None:
85 if c.repo_info is None:
96 h.flash(_('%s repository is not mapped to db perhaps'
86 h.flash(_('%s repository is not mapped to db perhaps'
@@ -234,11 +224,11 b' class ReposController(BaseController):'
234 repo_groups=c.repo_groups_choices)()
224 repo_groups=c.repo_groups_choices)()
235 try:
225 try:
236 form_result = _form.to_python(dict(request.POST))
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 invalidate_cache('get_repo_cached_%s' % repo_name)
228 invalidate_cache('get_repo_cached_%s' % repo_name)
239 h.flash(_('Repository %s updated successfully' % repo_name),
229 h.flash(_('Repository %s updated successfully' % repo_name),
240 category='success')
230 category='success')
241 changed_name = form_result['repo_name_full']
231 changed_name = repo.repo_name
242 action_logger(self.rhodecode_user, 'admin_updated_repo',
232 action_logger(self.rhodecode_user, 'admin_updated_repo',
243 changed_name, '', self.sa)
233 changed_name, '', self.sa)
244
234
@@ -9,9 +9,10 b' from pylons import request, response, se'
9 from pylons.controllers.util import abort, redirect
9 from pylons.controllers.util import abort, redirect
10 from pylons.i18n.translation import _
10 from pylons.i18n.translation import _
11
11
12 from sqlalchemy.exc import IntegrityError
13
12 from rhodecode.lib import helpers as h
14 from rhodecode.lib import helpers as h
13 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
14 HasPermissionAnyDecorator
15 from rhodecode.lib.base import BaseController, render
16 from rhodecode.lib.base import BaseController, render
16 from rhodecode.model.db import Group
17 from rhodecode.model.db import Group
17 from rhodecode.model.repos_group import ReposGroupModel
18 from rhodecode.model.repos_group import ReposGroupModel
@@ -31,19 +32,7 b' class ReposGroupsController(BaseControll'
31 super(ReposGroupsController, self).__before__()
32 super(ReposGroupsController, self).__before__()
32
33
33 def __load_defaults(self):
34 def __load_defaults(self):
34
35 c.repo_groups = Group.groups_choices()
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])
47 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
36 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
48
37
49 def __load_data(self, group_id):
38 def __load_data(self, group_id):
@@ -58,6 +47,8 b' class ReposGroupsController(BaseControll'
58
47
59 data = repo_group.get_dict()
48 data = repo_group.get_dict()
60
49
50 data['group_name'] = repo_group.name
51
61 return data
52 return data
62
53
63 @HasPermissionAnyDecorator('hg.admin')
54 @HasPermissionAnyDecorator('hg.admin')
@@ -169,13 +160,28 b' class ReposGroupsController(BaseControll'
169 repos_group_model.delete(id)
160 repos_group_model.delete(id)
170 h.flash(_('removed repos group %s' % gr.group_name), category='success')
161 h.flash(_('removed repos group %s' % gr.group_name), category='success')
171 #TODO: in future action_logger(, '', '', '', self.sa)
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 except Exception:
174 except Exception:
173 log.error(traceback.format_exc())
175 log.error(traceback.format_exc())
174 h.flash(_('error occurred during deletion of repos group %s' % gr.group_name),
176 h.flash(_('error occurred during deletion of repos '
175 category='error')
177 'group %s' % gr.group_name), category='error')
176
178
177 return redirect(url('repos_groups'))
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 def show(self, id, format='html'):
185 def show(self, id, format='html'):
180 """GET /repos_groups/id: Show a specific item"""
186 """GET /repos_groups/id: Show a specific item"""
181 # url('repos_group', id=ID)
187 # url('repos_group', id=ID)
@@ -366,17 +366,7 b' class SettingsController(BaseController)'
366 def create_repository(self):
366 def create_repository(self):
367 """GET /_admin/create_repository: Form to create a new item"""
367 """GET /_admin/create_repository: Form to create a new item"""
368
368
369 c.repo_groups = [('', '')]
369 c.repo_groups = Group.groups_choices()
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])
380 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
370 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
381
371
382 new_repo = request.GET.get('repo', '')
372 new_repo = request.GET.get('repo', '')
@@ -316,13 +316,6 b' class FilesController(BaseRepoController'
316 filename = file_obj.filename
316 filename = file_obj.filename
317 content = file_obj.file
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 node_path = os.path.join(location, filename)
319 node_path = os.path.join(location, filename)
327 author = self.rhodecode_user.full_contact
320 author = self.rhodecode_user.full_contact
328
321
@@ -23,6 +23,8 b''
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
27
26 def __get_lem():
28 def __get_lem():
27 from pygments import lexers
29 from pygments import lexers
28 from string import lower
30 from string import lower
@@ -379,3 +381,28 b' def get_changeset_safe(repo, rev):'
379 from rhodecode.lib.utils import EmptyChangeset
381 from rhodecode.lib.utils import EmptyChangeset
380 cs = EmptyChangeset(requested_revision=rev)
382 cs = EmptyChangeset(requested_revision=rev)
381 return cs
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 def locked_task(func):
94 def locked_task(func):
95 def __wrapper(func, *fargs, **fkwargs):
95 def __wrapper(func, *fargs, **fkwargs):
96 lockkey = __get_lockkey(func, *fargs, **fkwargs)
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 log.info('running task with lockkey %s', lockkey)
99 log.info('running task with lockkey %s', lockkey)
100 try:
100 try:
101 l = DaemonLock(jn(lockkey_path, lockkey))
101 l = DaemonLock(file_=jn(lockkey_path, lockkey))
102 ret = func(*fargs, **fkwargs)
102 ret = func(*fargs, **fkwargs)
103 l.release()
103 l.release()
104 return ret
104 return ret
@@ -97,10 +97,11 b' def get_commits_stats(repo_name, ts_min_'
97
97
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 ts_max_y)
99 ts_max_y)
100 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
100 lockkey_path = config['here']
101
101 log.info('running task with lockkey %s', lockkey)
102 log.info('running task with lockkey %s', lockkey)
102 try:
103 try:
103 lock = l = DaemonLock(jn(lockkey_path, lockkey))
104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
104
105
105 #for js data compatibilty cleans the key for person from '
106 #for js data compatibilty cleans the key for person from '
106 akc = lambda k: person(k).replace('"', "")
107 akc = lambda k: person(k).replace('"', "")
@@ -24,6 +24,9 b''
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
28 from rhodecode import __platform__, PLATFORM_WIN
29
27 #==============================================================================
30 #==============================================================================
28 # json
31 # json
29 #==============================================================================
32 #==============================================================================
@@ -358,3 +361,19 b' class OrderedDict(_odict, dict):'
358 # OrderedSet
361 # OrderedSet
359 #==============================================================================
362 #==============================================================================
360 from sqlalchemy.util import OrderedSet
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 return repo_name
598 return repo_name
599 else:
599 else:
600 def make_link(group):
600 def make_link(group):
601 return link_to(group.group_name, url('repos_group',
601 return link_to(group.name, url('repos_group_home',
602 id=group.group_id))
602 group_name=group.group_name))
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 " &raquo; " + repo_name)
604 " &raquo; " + repo_name)
605
605
606
607 def fancy_file_stats(stats):
606 def fancy_file_stats(stats):
608 """
607 """
609 Displays a fancy two colored bar for number of added/deleted
608 Displays a fancy two colored bar for number of added/deleted
@@ -101,7 +101,7 b' class MakeIndex(BasePasterCommand):'
101 from rhodecode.lib.pidlock import LockHeld, DaemonLock
101 from rhodecode.lib.pidlock import LockHeld, DaemonLock
102 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
102 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
103 try:
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 WhooshIndexingDaemon(index_location=index_location,
105 WhooshIndexingDaemon(index_location=index_location,
106 repo_location=repo_location,
106 repo_location=repo_location,
107 repo_list=repo_list)\
107 repo_list=repo_list)\
@@ -6,20 +6,7 b' import errno'
6 from warnings import warn
6 from warnings import warn
7 from multiprocessing.util import Finalize
7 from multiprocessing.util import Finalize
8
8
9 from rhodecode import __platform__, PLATFORM_WIN
9 from rhodecode.lib.compat import kill
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
23
10
24 class LockHeld(Exception):
11 class LockHeld(Exception):
25 pass
12 pass
@@ -29,17 +16,17 b' class DaemonLock(object):'
29 """daemon locking
16 """daemon locking
30 USAGE:
17 USAGE:
31 try:
18 try:
32 l = DaemonLock(desc='test lock')
19 l = DaemonLock(file_='/path/tolockfile',desc='test lock')
33 main()
20 main()
34 l.release()
21 l.release()
35 except LockHeld:
22 except LockHeld:
36 sys.exit(1)
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 desc='daemon lock', debug=False):
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 os.path.dirname(__file__),
30 os.path.dirname(__file__),
44 'running.lock')
31 'running.lock')
45 self.callbackfn = callbackfn
32 self.callbackfn = callbackfn
@@ -360,16 +360,19 b' def map_groups(groups):'
360
360
361 parent = None
361 parent = None
362 group = None
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 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
369 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
365
370
366 if group is None:
371 if group is None:
367 group = Group(group_name, parent)
372 group = Group(group_name, parent)
368 sa.add(group)
373 sa.add(group)
369 sa.commit()
374 sa.commit()
370
371 parent = group
375 parent = group
372
373 return group
376 return group
374
377
375
378
@@ -386,8 +389,13 b' def repo2db_mapper(initial_repo_list, re'
386 rm = RepoModel()
389 rm = RepoModel()
387 user = sa.query(User).filter(User.admin == True).first()
390 user = sa.query(User).filter(User.admin == True).first()
388 added = []
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 for name, repo in initial_repo_list.items():
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 if not rm.get_by_repo_name(name, cache=False):
399 if not rm.get_by_repo_name(name, cache=False):
392 log.info('repository %s not found creating default', name)
400 log.info('repository %s not found creating default', name)
393 added.append(name)
401 added.append(name)
@@ -442,26 +450,6 b' def add_cache(settings):'
442 beaker.cache.cache_regions[region] = region_settings
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 # TEST FUNCTIONS AND CREATORS
454 # TEST FUNCTIONS AND CREATORS
467 #==============================================================================
455 #==============================================================================
@@ -483,7 +471,7 b' def create_test_index(repo_location, con'
483 os.makedirs(index_location)
471 os.makedirs(index_location)
484
472
485 try:
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 WhooshIndexingDaemon(index_location=index_location,
475 WhooshIndexingDaemon(index_location=index_location,
488 repo_location=repo_location)\
476 repo_location=repo_location)\
489 .run(full_index=full_index)
477 .run(full_index=full_index)
@@ -31,9 +31,10 b' from datetime import date'
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
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 from sqlalchemy.orm.interfaces import MapperExtension
37 from sqlalchemy.orm.interfaces import MapperExtension
36
37 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
38
39
39 from vcs import get_backend
40 from vcs import get_backend
@@ -42,13 +43,14 b' from vcs.exceptions import VCSError'
42 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.lazy import LazyProperty
43
44
44 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 generate_api_key
46 generate_api_key, safe_unicode
46 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 from rhodecode.lib.compat import json
48 from rhodecode.lib.compat import json
48
49
49 from rhodecode.model.meta import Base, Session
50 from rhodecode.model.meta import Base, Session
50 from rhodecode.model.caching_query import FromCache
51 from rhodecode.model.caching_query import FromCache
51
52
53
52 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
53
55
54 #==============================================================================
56 #==============================================================================
@@ -126,7 +128,8 b' class BaseModel(object):'
126
128
127 @classmethod
129 @classmethod
128 def get(cls, id_):
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 @classmethod
134 @classmethod
132 def delete(cls, id_):
135 def delete(cls, id_):
@@ -140,12 +143,34 b' class RhodeCodeSettings(Base, BaseModel)'
140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
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 def __init__(self, k='', v=''):
148 def __init__(self, k='', v=''):
146 self.app_settings_name = k
149 self.app_settings_name = k
147 self.app_settings_value = v
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 def __repr__(self):
174 def __repr__(self):
150 return "<%s('%s:%s')>" % (self.__class__.__name__,
175 return "<%s('%s:%s')>" % (self.__class__.__name__,
151 self.app_settings_name, self.app_settings_value)
176 self.app_settings_name, self.app_settings_value)
@@ -176,14 +201,11 b' class RhodeCodeSettings(Base, BaseModel)'
176 @classmethod
201 @classmethod
177 def get_ldap_settings(cls, cache=False):
202 def get_ldap_settings(cls, cache=False):
178 ret = Session.query(cls)\
203 ret = Session.query(cls)\
179 .filter(cls.app_settings_name.startswith('ldap_'))\
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
180 .all()
181 fd = {}
205 fd = {}
182 for row in ret:
206 for row in ret:
183 fd.update({row.app_settings_name:row.app_settings_value})
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 return fd
209 return fd
188
210
189
211
@@ -283,7 +305,7 b' class User(Base, BaseModel):'
283 @classmethod
305 @classmethod
284 def get_by_username(cls, username, case_insensitive=False):
306 def get_by_username(cls, username, case_insensitive=False):
285 if case_insensitive:
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 else:
309 else:
288 return Session.query(cls).filter(cls.username == username).scalar()
310 return Session.query(cls).filter(cls.username == username).scalar()
289
311
@@ -486,12 +508,16 b' class Repository(Base, BaseModel):'
486 self.repo_id, self.repo_name)
508 self.repo_id, self.repo_name)
487
509
488 @classmethod
510 @classmethod
511 def url_sep(cls):
512 return '/'
513
514 @classmethod
489 def get_by_repo_name(cls, repo_name):
515 def get_by_repo_name(cls, repo_name):
490 q = Session.query(cls).filter(cls.repo_name == repo_name)
516 q = Session.query(cls).filter(cls.repo_name == repo_name)
491
517
492 q = q.options(joinedload(Repository.fork))\
518 q = q.options(joinedload(Repository.fork))\
493 .options(joinedload(Repository.user))\
519 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.group))\
520 .options(joinedload(Repository.group))\
495
521
496 return q.one()
522 return q.one()
497
523
@@ -506,13 +532,14 b' class Repository(Base, BaseModel):'
506
532
507 :param cls:
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 q.options(FromCache("sql_cache_short", "repository_repo_path"))
537 q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 return q.one().ui_value
538 return q.one().ui_value
512
539
513 @property
540 @property
514 def just_name(self):
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 @property
544 @property
518 def groups_with_parents(self):
545 def groups_with_parents(self):
@@ -541,7 +568,8 b' class Repository(Base, BaseModel):'
541 Returns base full path for that repository means where it actually
568 Returns base full path for that repository means where it actually
542 exists on a filesystem
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 q.options(FromCache("sql_cache_short", "repository_repo_path"))
573 q.options(FromCache("sql_cache_short", "repository_repo_path"))
546 return q.one().ui_value
574 return q.one().ui_value
547
575
@@ -551,9 +579,18 b' class Repository(Base, BaseModel):'
551 # we need to split the name by / since this is how we store the
579 # we need to split the name by / since this is how we store the
552 # names in the database, but that eventually needs to be converted
580 # names in the database, but that eventually needs to be converted
553 # into a valid system path
581 # into a valid system path
554 p += self.repo_name.split('/')
582 p += self.repo_name.split(Repository.url_sep())
555 return os.path.join(*p)
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 @property
594 @property
558 def _ui(self):
595 def _ui(self):
559 """
596 """
@@ -718,9 +755,26 b' class Group(Base, BaseModel):'
718 self.group_name)
755 self.group_name)
719
756
720 @classmethod
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 def url_sep(cls):
771 def url_sep(cls):
722 return '/'
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 @property
778 @property
725 def parents(self):
779 def parents(self):
726 parents_recursion_limit = 5
780 parents_recursion_limit = 5
@@ -750,9 +804,16 b' class Group(Base, BaseModel):'
750 return Session.query(Group).filter(Group.parent_group == self)
804 return Session.query(Group).filter(Group.parent_group == self)
751
805
752 @property
806 @property
807 def name(self):
808 return self.group_name.split(Group.url_sep())[-1]
809
810 @property
753 def full_path(self):
811 def full_path(self):
754 return Group.url_sep().join([g.group_name for g in self.parents] +
812 return self.group_name
755 [self.group_name])
813
814 @property
815 def full_path_splitted(self):
816 return self.group_name.split(Group.url_sep())
756
817
757 @property
818 @property
758 def repositories(self):
819 def repositories(self):
@@ -771,6 +832,17 b' class Group(Base, BaseModel):'
771
832
772 return cnt + children_count(self)
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 class Permission(Base, BaseModel):
846 class Permission(Base, BaseModel):
775 __tablename__ = 'permissions'
847 __tablename__ = 'permissions'
776 __table_args__ = {'extend_existing':True}
848 __table_args__ = {'extend_existing':True}
@@ -122,7 +122,7 b' def ValidReposGroup(edit, old_data):'
122 def validate_python(self, value, state):
122 def validate_python(self, value, state):
123 #TODO WRITE VALIDATIONS
123 #TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
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 # slugify repo group just in case :)
127 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
128 slug = repo_name_slug(group_name)
@@ -250,7 +250,7 b' def ValidRepoName(edit, old_data):'
250 gr = Group.get(value.get('repo_group'))
250 gr = Group.get(value.get('repo_group'))
251 group_path = gr.full_path
251 group_path = gr.full_path
252 # value needs to be aware of group name in order to check
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 # database
254 # database
255 repo_name_full = group_path + Group.url_sep() + repo_name
255 repo_name_full = group_path + Group.url_sep() + repo_name
256 else:
256 else:
@@ -259,25 +259,32 b' def ValidRepoName(edit, old_data):'
259
259
260
260
261 value['repo_name_full'] = repo_name_full
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 if group_path != '':
266 if group_path != '':
265 if RepoModel().get_by_repo_name(repo_name_full,):
267 if RepoModel().get_by_repo_name(repo_name_full,):
266 e_dict = {'repo_name':_('This repository already '
268 e_dict = {'repo_name':_('This repository already '
267 'exists in group "%s"') %
269 'exists in a group "%s"') %
268 gr.group_name}
270 gr.group_name}
269 raise formencode.Invalid('', value, state,
271 raise formencode.Invalid('', value, state,
270 error_dict=e_dict)
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:
280 elif RepoModel().get_by_repo_name(repo_name_full):
273 if RepoModel().get_by_repo_name(repo_name_full):
274 e_dict = {'repo_name':_('This repository '
281 e_dict = {'repo_name':_('This repository '
275 'already exists')}
282 'already exists')}
276 raise formencode.Invalid('', value, state,
283 raise formencode.Invalid('', value, state,
277 error_dict=e_dict)
284 error_dict=e_dict)
285
278 return value
286 return value
279
287
280
281 return _ValidRepoName
288 return _ValidRepoName
282
289
283 def ValidForkName():
290 def ValidForkName():
@@ -98,7 +98,7 b' class RepoModel(BaseModel):'
98 try:
98 try:
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100
100
101 #update permissions
101 # update permissions
102 for member, perm, member_type in form_data['perms_updates']:
102 for member, perm, member_type in form_data['perms_updates']:
103 if member_type == 'user':
103 if member_type == 'user':
104 r2p = self.sa.query(RepoToPerm)\
104 r2p = self.sa.query(RepoToPerm)\
@@ -122,7 +122,7 b' class RepoModel(BaseModel):'
122 perm).scalar()
122 perm).scalar()
123 self.sa.add(g2p)
123 self.sa.add(g2p)
124
124
125 #set new permissions
125 # set new permissions
126 for member, perm, member_type in form_data['perms_new']:
126 for member, perm, member_type in form_data['perms_new']:
127 if member_type == 'user':
127 if member_type == 'user':
128 r2p = RepoToPerm()
128 r2p = RepoToPerm()
@@ -144,26 +144,29 b' class RepoModel(BaseModel):'
144 .scalar()
144 .scalar()
145 self.sa.add(g2p)
145 self.sa.add(g2p)
146
146
147 #update current repo
147 # update current repo
148 for k, v in form_data.items():
148 for k, v in form_data.items():
149 if k == 'user':
149 if k == 'user':
150 cur_repo.user = User.get_by_username(v)
150 cur_repo.user = User.get_by_username(v)
151 elif k == 'repo_name':
151 elif k == 'repo_name':
152 cur_repo.repo_name = form_data['repo_name_full']
152 pass
153 elif k == 'repo_group':
153 elif k == 'repo_group':
154 cur_repo.group_id = v
154 cur_repo.group_id = v
155
155
156 else:
156 else:
157 setattr(cur_repo, k, v)
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 self.sa.add(cur_repo)
162 self.sa.add(cur_repo)
160
163
161 if repo_name != form_data['repo_name_full']:
164 if repo_name != new_name:
162 # rename repository
165 # rename repository
163 self.__rename_repo(old=repo_name,
166 self.__rename_repo(old=repo_name, new=new_name)
164 new=form_data['repo_name_full'])
165
167
166 self.sa.commit()
168 self.sa.commit()
169 return cur_repo
167 except:
170 except:
168 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
169 self.sa.rollback()
172 self.sa.rollback()
@@ -235,7 +238,7 b' class RepoModel(BaseModel):'
235 from rhodecode.model.scm import ScmModel
238 from rhodecode.model.scm import ScmModel
236 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
239 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
237 cur_user.user_id)
240 cur_user.user_id)
238
241 return new_repo
239 except:
242 except:
240 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
241 self.sa.rollback()
244 self.sa.rollback()
@@ -302,7 +305,7 b' class RepoModel(BaseModel):'
302 :param parent_id:
305 :param parent_id:
303 :param clone_uri:
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 if new_parent_id:
310 if new_parent_id:
308 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
311 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
@@ -313,12 +316,20 b' class RepoModel(BaseModel):'
313 repo_path = os.path.join(*map(lambda x:safe_str(x),
316 repo_path = os.path.join(*map(lambda x:safe_str(x),
314 [self.repos_path, new_parent_path, repo_name]))
317 [self.repos_path, new_parent_path, repo_name]))
315
318
316 if is_valid_repo(repo_path, self.repos_path) is False:
319
317 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
320 # check if this path is not a repository
318 clone_uri)
321 if is_valid_repo(repo_path, self.repos_path):
319 backend = get_backend(alias)
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 def __rename_repo(self, old, new):
335 def __rename_repo(self, old, new):
@@ -50,7 +50,7 b' class ReposGroupModel(BaseModel):'
50 q = RhodeCodeUi.get_by_key('/').one()
50 q = RhodeCodeUi.get_by_key('/').one()
51 return q.ui_value
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 makes repositories group on filesystem
55 makes repositories group on filesystem
56
56
@@ -58,44 +58,30 b' class ReposGroupModel(BaseModel):'
58 :param parent_id:
58 :param parent_id:
59 """
59 """
60
60
61 if parent_id:
61 create_path = os.path.join(self.repos_path, group_name)
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)
68 log.debug('creating new group in %s', create_path)
62 log.debug('creating new group in %s', create_path)
69
63
70 if os.path.isdir(create_path):
64 if os.path.isdir(create_path):
71 raise Exception('That directory already exists !')
65 raise Exception('That directory already exists !')
72
66
73
74 os.makedirs(create_path)
67 os.makedirs(create_path)
75
68
76
69 def __rename_group(self, old, new):
77 def __rename_group(self, old, old_parent_id, new, new_parent_id):
78 """
70 """
79 Renames a group on filesystem
71 Renames a group on filesystem
80
72
81 :param group_name:
73 :param group_name:
82 """
74 """
75
76 if old == new:
77 log.debug('skipping group rename')
78 return
79
83 log.debug('renaming repos group from %s to %s', old, new)
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:
83 old_path = os.path.join(self.repos_path, old)
92 paths = Group.get(old_parent_id).full_path.split(Group.url_sep())
84 new_path = os.path.join(self.repos_path, new)
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)
99
85
100 log.debug('renaming repos paths from %s to %s', old_path, new_path)
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 paths = os.sep.join(paths)
100 paths = os.sep.join(paths)
115
101
116 rm_path = os.path.join(self.repos_path, paths)
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 def create(self, form_data):
107 def create(self, form_data):
120 try:
108 try:
121 new_repos_group = Group()
109 new_repos_group = Group()
122 new_repos_group.group_name = form_data['group_name']
110 new_repos_group.group_description = form_data['group_description']
123 new_repos_group.group_description = \
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
124 form_data['group_description']
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
125 new_repos_group.group_parent_id = form_data['group_parent_id']
126
113
127 self.sa.add(new_repos_group)
114 self.sa.add(new_repos_group)
128
115
129 self.__create_group(form_data['group_name'],
116 self.__create_group(new_repos_group.group_name)
130 form_data['group_parent_id'])
131
117
132 self.sa.commit()
118 self.sa.commit()
119 return new_repos_group
133 except:
120 except:
134 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
135 self.sa.rollback()
122 self.sa.rollback()
@@ -139,23 +126,27 b' class ReposGroupModel(BaseModel):'
139
126
140 try:
127 try:
141 repos_group = Group.get(repos_group_id)
128 repos_group = Group.get(repos_group_id)
142 old_name = repos_group.group_name
129 old_path = repos_group.full_path
143 old_parent_id = repos_group.group_parent_id
144
130
145 repos_group.group_name = form_data['group_name']
131 #change properties
146 repos_group.group_description = \
132 repos_group.group_description = form_data['group_description']
147 form_data['group_description']
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
148 repos_group.group_parent_id = 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 self.sa.add(repos_group)
138 self.sa.add(repos_group)
151
139
152 if old_name != form_data['group_name'] or (old_parent_id !=
140 self.__rename_group(old_path, new_path)
153 form_data['group_parent_id']):
141
154 self.__rename_group(old=old_name, old_parent_id=old_parent_id,
142 # we need to get all repositories from this new group and
155 new=form_data['group_name'],
143 # rename them accordingly to new group path
156 new_parent_id=form_data['group_parent_id'])
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 self.sa.commit()
148 self.sa.commit()
149 return repos_group
159 except:
150 except:
160 log.error(traceback.format_exc())
151 log.error(traceback.format_exc())
161 self.sa.rollback()
152 self.sa.rollback()
@@ -22,6 +22,7 b''
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import time
26 import time
26 import traceback
27 import traceback
27 import logging
28 import logging
@@ -146,10 +147,15 b' class ScmModel(BaseModel):'
146 repos_list = {}
147 repos_list = {}
147
148
148 for name, path in get_filesystem_repos(repos_path, recursive=True):
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 try:
155 try:
150 if name in repos_list:
156 if name in repos_list:
151 raise RepositoryError('Duplicate repository name %s '
157 raise RepositoryError('Duplicate repository name %s '
152 'found in %s' % (name, path))
158 'found in %s' % (name, path))
153 else:
159 else:
154
160
155 klass = get_backend(path[0])
161 klass = get_backend(path[0])
@@ -246,12 +246,13 b' color:#FFF;'
246 }
246 }
247
247
248 #header #header-inner {
248 #header #header-inner {
249 height:40px;
249 min-height:40px;
250 clear:both;
250 clear:both;
251 position:relative;
251 position:relative;
252 background:#003367 url("../images/header_inner.png") repeat-x;
252 background:#003367 url("../images/header_inner.png") repeat-x;
253 margin:0;
253 margin:0;
254 padding:0;
254 padding:0;
255 display:block;
255 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
256 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
256 -webkit-border-radius: 4px 4px 4px 4px;
257 -webkit-border-radius: 4px 4px 4px 4px;
257 -khtml-border-radius: 4px 4px 4px 4px;
258 -khtml-border-radius: 4px 4px 4px 4px;
@@ -272,7 +273,10 b' padding:0;'
272 #header #header-inner #home a:hover {
273 #header #header-inner #home a:hover {
273 background-position:0 -40px;
274 background-position:0 -40px;
274 }
275 }
275
276 #header #header-inner #logo {
277 float: left;
278 position: absolute;
279 }
276 #header #header-inner #logo h1 {
280 #header #header-inner #logo h1 {
277 color:#FFF;
281 color:#FFF;
278 font-size:18px;
282 font-size:18px;
@@ -348,7 +352,7 b' top:0;'
348 left:0;
352 left:0;
349 border-left:none;
353 border-left:none;
350 border-right:1px solid #2e5c89;
354 border-right:1px solid #2e5c89;
351 padding:8px 8px 4px;
355 padding:8px 6px 4px;
352 }
356 }
353
357
354 #header #header-inner #quick li span.icon_short {
358 #header #header-inner #quick li span.icon_short {
@@ -356,7 +360,10 b' top:0;'
356 left:0;
360 left:0;
357 border-left:none;
361 border-left:none;
358 border-right:1px solid #2e5c89;
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 #header #header-inner #quick li a:hover {
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 .quick_repo_menu{
582 .quick_repo_menu{
568 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
583 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
569 cursor: pointer;
584 cursor: pointer;
@@ -1895,19 +1910,6 b' div.browserblock .add_node{'
1895 padding-left: 5px;
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 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1913 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1912 text-decoration: none !important;
1914 text-decoration: none !important;
1913 }
1915 }
@@ -2371,8 +2373,10 b' border-left:1px solid #316293;'
2371 border:1px solid #316293;
2373 border:1px solid #316293;
2372 }
2374 }
2373
2375
2374
2376 .ui-button-small a:hover {
2375 input.ui-button-small {
2377
2378 }
2379 input.ui-button-small,.ui-button-small {
2376 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2380 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2377 border-top:1px solid #DDD !important;
2381 border-top:1px solid #DDD !important;
2378 border-left:1px solid #c6c6c6 !important;
2382 border-left:1px solid #c6c6c6 !important;
@@ -2387,17 +2391,19 b' margin:0 !important;'
2387 border-radius: 4px 4px 4px 4px !important;
2391 border-radius: 4px 4px 4px 4px !important;
2388 box-shadow: 0 1px 0 #ececec !important;
2392 box-shadow: 0 1px 0 #ececec !important;
2389 cursor: pointer !important;
2393 cursor: pointer !important;
2390 }
2394 padding:0px 2px 1px 2px;
2391
2395 }
2392 input.ui-button-small:hover {
2396
2397 input.ui-button-small:hover,.ui-button-small:hover {
2393 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2398 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2394 border-top:1px solid #ccc !important;
2399 border-top:1px solid #ccc !important;
2395 border-left:1px solid #bebebe !important;
2400 border-left:1px solid #bebebe !important;
2396 border-right:1px solid #b1b1b1 !important;
2401 border-right:1px solid #b1b1b1 !important;
2397 border-bottom:1px solid #afafaf !important;
2402 border-bottom:1px solid #afafaf !important;
2398 }
2403 text-decoration: none;
2399
2404 }
2400 input.ui-button-small-blue {
2405
2406 input.ui-button-small-blue,.ui-button-small-blue {
2401 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2407 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2402 border-top:1px solid #5c91a4;
2408 border-top:1px solid #5c91a4;
2403 border-left:1px solid #2a6f89;
2409 border-left:1px solid #2a6f89;
@@ -2410,6 +2416,7 b' color:#fff;'
2410 border-radius: 4px 4px 4px 4px;
2416 border-radius: 4px 4px 4px 4px;
2411 box-shadow: 0 1px 0 #ececec;
2417 box-shadow: 0 1px 0 #ececec;
2412 cursor: pointer;
2418 cursor: pointer;
2419 padding:0px 2px 1px 2px;
2413 }
2420 }
2414
2421
2415 input.ui-button-small-blue:hover {
2422 input.ui-button-small-blue:hover {
@@ -5,12 +5,14 b''
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs()">
7 <%def name="breadcrumbs()">
8 <span class="groups_breadcrumbs">
8 ${_('Groups')}
9 ${_('Groups')}
9 %if c.group.parent_group:
10 %if c.group.parent_group:
10 &raquo; ${h.link_to(c.group.parent_group.group_name,
11 &raquo; ${h.link_to(c.group.parent_group.name,
11 h.url('repos_group',id=c.group.parent_group.group_id))}
12 h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
12 %endif
13 %endif
13 &raquo; "${c.group.group_name}" ${_('with')}
14 &raquo; "${c.group.name}" ${_('with')}
15 </span>
14 </%def>
16 </%def>
15
17
16 <%def name="page_nav()">
18 <%def name="page_nav()">
@@ -2,14 +2,14 b''
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
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 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.group_name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
@@ -44,14 +44,14 b''
44 <td>
44 <td>
45 <div style="white-space: nowrap">
45 <div style="white-space: nowrap">
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
47 ${h.link_to(h.literal(' &raquo; '.join([g.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 </div>
48 </div>
49 </td>
49 </td>
50 <td>${gr.group_description}</td>
50 <td>${gr.group_description}</td>
51 <td><b>${gr.repositories.count()}</b></td>
51 <td><b>${gr.repositories.count()}</b></td>
52 <td>
52 <td>
53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
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 ${h.end_form()}
55 ${h.end_form()}
56 </td>
56 </td>
57 </tr>
57 </tr>
@@ -11,9 +11,9 b''
11 ${h.form(h.url.current())}
11 ${h.form(h.url.current())}
12 <div class="info_box">
12 <div class="info_box">
13 <span class="rev">${_('view')}@rev</span>
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 ${h.text('at_rev',value=c.changeset.revision,size=5)}
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 ## ${h.submit('view',_('view'),class_="ui-button-small")}
17 ## ${h.submit('view',_('view'),class_="ui-button-small")}
18 </div>
18 </div>
19 ${h.end_form()}
19 ${h.end_form()}
@@ -24,11 +24,11 b''
24 </div>
24 </div>
25 <div class="browser-search">
25 <div class="browser-search">
26 <div id="search_activate_id" class="search_activate">
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 </div>
28 </div>
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 <div id="add_node_id" class="add_node">
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>
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 </div>
32 </div>
33 % endif
33 % endif
34 <div>
34 <div>
@@ -35,7 +35,7 b''
35 <td>
35 <td>
36 <div style="white-space: nowrap">
36 <div style="white-space: nowrap">
37 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
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 </div>
39 </div>
40 </td>
40 </td>
41 <td>${gr.group_description}</td>
41 <td>${gr.group_description}</td>
@@ -45,17 +45,17 b''
45
45
46 ##REPO TYPE
46 ##REPO TYPE
47 %if c.dbrepo.repo_type =='hg':
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 %endif
49 %endif
50 %if c.dbrepo.repo_type =='git':
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 %endif
52 %endif
53
53
54 ##PUBLIC/PRIVATE
54 ##PUBLIC/PRIVATE
55 %if c.dbrepo.private:
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 %else:
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 %endif
59 %endif
60
60
61 ##REPO NAME
61 ##REPO NAME
@@ -67,7 +67,7 b''
67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 <img class="icon" alt="${_('public')}"
68 <img class="icon" alt="${_('public')}"
69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
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 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 </a>
72 </a>
73 </div>
73 </div>
@@ -78,7 +78,7 b''
78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 <img class="icon" alt="${_('remote clone')}"
79 <img class="icon" alt="${_('remote clone')}"
80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
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 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 </a>
83 </a>
84 </div>
84 </div>
@@ -151,7 +151,7 b''
151 %elif c.enable_downloads is False:
151 %elif c.enable_downloads is False:
152 ${_('Downloads are disabled for this repository')}
152 ${_('Downloads are disabled for this repository')}
153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
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 %endif
155 %endif
156 %else:
156 %else:
157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
@@ -317,7 +317,7 b''
317 %if c.no_data:
317 %if c.no_data:
318 ${c.no_data_msg}
318 ${c.no_data_msg}
319 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
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 %endif
321 %endif
322
322
323 %else:
323 %else:
@@ -13,7 +13,7 b' class TestChangelogController(TestContro'
13 """name="5e204e7583b9" type="checkbox" value="1" />"""
13 """name="5e204e7583b9" type="checkbox" value="1" />"""
14 in response.body)
14 in response.body)
15 self.assertTrue("""<span>commit 154: 5e204e7583b9@2010-08-10 """
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 self.assertTrue("""Small update at simplevcs app""" in response.body)
17 self.assertTrue("""Small update at simplevcs app""" in response.body)
18
18
19
19
@@ -43,7 +43,7 b' class TestChangelogController(TestContro'
43 """name="46ad32a4f974" type="checkbox" value="1" />"""
43 """name="46ad32a4f974" type="checkbox" value="1" />"""
44 in response.body)
44 in response.body)
45 self.assertTrue("""<span>commit 64: 46ad32a4f974@2010-04-20"""
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 self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
48 self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
49 """579b1" class="changed_total tooltip" """
49 """579b1" class="changed_total tooltip" """
@@ -145,8 +145,8 b' class TestLoginController(TestController'
145 'lastname':'test'})
145 'lastname':'test'})
146
146
147 self.assertEqual(response.status , '200 OK')
147 self.assertEqual(response.status , '200 OK')
148 assert 'An email address must contain a single @' in response.body
148 self.assertTrue('An email address must contain a single @' in response.body)
149 assert 'This username already exists' 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 'lastname':'test'})
160 'lastname':'test'})
161
161
162 self.assertEqual(response.status , '200 OK')
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 def test_register_password_mismatch(self):
166 def test_register_password_mismatch(self):
@@ -1,2 +1,153 b''
1 import os
1 import unittest
2 import unittest
2 from rhodecode.tests import *
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 requirements = [
12 requirements = [
13 "Pylons==1.0.0",
13 "Pylons==1.0.0",
14 "Beaker==1.5.4",
14 "WebHelpers>=1.2",
15 "WebHelpers>=1.2",
15 "formencode==1.2.4",
16 "formencode==1.2.4",
16 "SQLAlchemy>=0.7.2,<0.8",
17 "SQLAlchemy==0.7.3",
17 "Mako>=0.4.2",
18 "Mako==0.5.0",
18 "pygments>=1.4",
19 "pygments>=1.4",
19 "mercurial>=1.9,<2.0",
20 "mercurial>=1.9.3,<2.0",
20 "whoosh<1.8",
21 "whoosh<1.8",
21 "celery>=2.2.5,<2.3",
22 "celery>=2.2.5,<2.3",
22 "babel",
23 "babel",
23 "python-dateutil>=1.5.0,<2.0.0",
24 "python-dateutil>=1.5.0,<2.0.0",
24 "dulwich>=0.8.0",
25 "dulwich>=0.8.0,<0.9.0",
25 "vcs>=0.2.1.dev",
26 "vcs>=0.2.3.dev",
26 "webob==1.0.8"
27 "webob==1.0.8"
27 ]
28 ]
28
29
29 dependency_links = [
30 dependency_links = [
30 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.2.dev",
31 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.3.dev",
31 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.2.dev",
32 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.3.dev",
32 ]
33 ]
33
34
34 classifiers = ['Development Status :: 4 - Beta',
35 classifiers = ['Development Status :: 4 - Beta',
35 'Environment :: Web Environment',
36 'Environment :: Web Environment',
36 'Framework :: Pylons',
37 'Framework :: Pylons',
37 'Intended Audience :: Developers',
38 'Intended Audience :: Developers',
39 'License :: OSI Approved :: GNU General Public License (GPL)',
38 'Operating System :: OS Independent',
40 'Operating System :: OS Independent',
39 'Programming Language :: Python',
41 'Programming Language :: Python',
40 'Programming Language :: Python :: 2.5',
42 'Programming Language :: Python :: 2.5',
General Comments 0
You need to be logged in to leave comments. Login now