##// END OF EJS Templates
merge with beta
marcink -
r2065:9ab21c5d merge rhodecode-0.0.1.3.2 default
parent child Browse files
Show More
@@ -15,4 +15,5 b' List of contributors to RhodeCode projec'
15 Les Peabody <lpeabody@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 Matt Zuba <matt.zuba@goodwillaz.org>
17 Matt Zuba <matt.zuba@goodwillaz.org>
18 Aras Pranckevicius <aras@unity3d.com> No newline at end of file
18 Aras Pranckevicius <aras@unity3d.com>
19 Tony Bussieres <t.bussieres@gmail.com>
@@ -4,6 +4,25 b' Changelog'
4 =========
4 =========
5
5
6
6
7 1.3.2 (**2012-02-28**)
8 ----------------------
9
10 news
11 ++++
12
13
14 fixes
15 +++++
16
17 - fixed git protocol issues with repos-groups
18 - fixed git remote repos validator that prevented from cloning remote git repos
19 - fixes #370 ending slashes fixes for repo and groups
20 - fixes #368 improved git-protocol detection to handle other clients
21 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
22 Moved To Root
23 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
24 - fixed #373 missing cascade drop on user_group_to_perm table
25
7 1.3.1 (**2012-02-27**)
26 1.3.1 (**2012-02-27**)
8 ----------------------
27 ----------------------
9
28
@@ -263,6 +263,11 b' class ReposGroupsController(BaseControll'
263 raise HTTPInternalServerError()
263 raise HTTPInternalServerError()
264
264
265 def show_by_name(self, group_name):
265 def show_by_name(self, group_name):
266 """
267 This is a proxy that does a lookup group_name -> id, and shows
268 the group by id view instead
269 """
270 group_name = group_name.rstrip('/')
266 id_ = RepoGroup.get_by_group_name(group_name).group_id
271 id_ = RepoGroup.get_by_group_name(group_name).group_id
267 return self.show(id_)
272 return self.show(id_)
268
273
@@ -160,11 +160,12 b' class UsersGroupsController(BaseControll'
160
160
161 try:
161 try:
162 UsersGroupModel().delete(id)
162 UsersGroupModel().delete(id)
163 Session.commit()
163 h.flash(_('successfully deleted users group'), category='success')
164 h.flash(_('successfully deleted users group'), category='success')
164 Session.commit()
165 except UsersGroupsAssignedException, e:
165 except UsersGroupsAssignedException, e:
166 h.flash(e, category='error')
166 h.flash(e, category='error')
167 except Exception:
167 except Exception:
168 log.error(traceback.format_exc())
168 h.flash(_('An error occurred during deletion of users group'),
169 h.flash(_('An error occurred during deletion of users group'),
169 category='error')
170 category='error')
170 return redirect(url('users_groups'))
171 return redirect(url('users_groups'))
@@ -24,6 +24,7 b' from beaker.exceptions import BeakerExce'
24 from sqlalchemy.orm.interfaces import MapperOption
24 from sqlalchemy.orm.interfaces import MapperOption
25 from sqlalchemy.orm.query import Query
25 from sqlalchemy.orm.query import Query
26 from sqlalchemy.sql import visitors
26 from sqlalchemy.sql import visitors
27 from rhodecode.lib import safe_str
27
28
28
29
29 class CachingQuery(Query):
30 class CachingQuery(Query):
@@ -137,9 +138,10 b' def _get_cache_parameters(query):'
137
138
138 if cache_key is None:
139 if cache_key is None:
139 # cache key - the value arguments from this query's parameters.
140 # cache key - the value arguments from this query's parameters.
140 args = [str(x) for x in _params_from_query(query)]
141 args = [safe_str(x) for x in _params_from_query(query)]
141 args.extend(filter(lambda k:k not in ['None', None, u'None'],
142 args.extend(filter(lambda k: k not in ['None', None, u'None'],
142 [str(query._limit), str(query._offset)]))
143 [str(query._limit), str(query._offset)]))
144
143 cache_key = " ".join(args)
145 cache_key = " ".join(args)
144
146
145 if cache_key is None:
147 if cache_key is None:
@@ -42,13 +42,21 b' class HttpsFixup(object):'
42 middleware you should set this header inside your
42 middleware you should set this header inside your
43 proxy ie. nginx, apache etc.
43 proxy ie. nginx, apache etc.
44 """
44 """
45 proto = environ.get('HTTP_X_URL_SCHEME')
46
45
47 if str2bool(self.config.get('force_https')):
46 if str2bool(self.config.get('force_https')):
48 proto = 'https'
47 proto = 'https'
49
48 else:
49 if 'HTTP_X_URL_SCHEME' in environ:
50 proto = environ.get('HTTP_X_URL_SCHEME')
51 elif 'HTTP_X_FORWARDED_SCHEME' in environ:
52 proto = environ.get('HTTP_X_FORWARDED_SCHEME')
53 elif 'HTTP_X_FORWARDED_PROTO' in environ:
54 proto = environ.get('HTTP_X_FORWARDED_PROTO')
55 else:
56 proto = 'http'
50 if proto == 'https':
57 if proto == 'https':
51 environ['wsgi.url_scheme'] = proto
58 environ['wsgi.url_scheme'] = proto
52 else:
59 else:
53 environ['wsgi.url_scheme'] = 'http'
60 environ['wsgi.url_scheme'] = 'http'
61
54 return None
62 return None
@@ -25,6 +25,7 b''
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import logging
29 import logging
29 import traceback
30 import traceback
30
31
@@ -79,21 +80,20 b' from webob.exc import HTTPNotFound, HTTP'
79 log = logging.getLogger(__name__)
80 log = logging.getLogger(__name__)
80
81
81
82
82 def is_git(environ):
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
83 """Returns True if request's target is git server.
84
84 ``HTTP_USER_AGENT`` would then have git client version given.
85
85
86 :param environ:
86 def is_git(environ):
87 """
87 path_info = environ['PATH_INFO']
88 http_user_agent = environ.get('HTTP_USER_AGENT')
88 isgit_path = GIT_PROTO_PAT.match(path_info)
89 if http_user_agent and http_user_agent.startswith('git'):
89 log.debug('is a git path %s pathinfo : %s' % (isgit_path, path_info))
90 return True
90 return isgit_path
91 return False
92
91
93
92
94 class SimpleGit(BaseVCSController):
93 class SimpleGit(BaseVCSController):
95
94
96 def _handle_request(self, environ, start_response):
95 def _handle_request(self, environ, start_response):
96
97 if not is_git(environ):
97 if not is_git(environ):
98 return self.application(environ, start_response)
98 return self.application(environ, start_response)
99
99
@@ -218,13 +218,11 b' class SimpleGit(BaseVCSController):'
218 """
218 """
219 try:
219 try:
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
222 if repo_name.endswith('/'):
223 repo_name = repo_name.rstrip('/')
224 except:
222 except:
225 log.error(traceback.format_exc())
223 log.error(traceback.format_exc())
226 raise
224 raise
227 repo_name = repo_name.split('/')[0]
225
228 return repo_name
226 return repo_name
229
227
230 def __get_user(self, username):
228 def __get_user(self, username):
@@ -238,7 +236,8 b' class SimpleGit(BaseVCSController):'
238 service = environ['QUERY_STRING'].split('=')
236 service = environ['QUERY_STRING'].split('=')
239 if len(service) > 1:
237 if len(service) > 1:
240 service_cmd = service[1]
238 service_cmd = service[1]
241 mapping = {'git-receive-pack': 'push',
239 mapping = {
240 'git-receive-pack': 'push',
242 'git-upload-pack': 'pull',
241 'git-upload-pack': 'pull',
243 }
242 }
244
243
@@ -92,11 +92,17 b' def repo_name_slug(value):'
92
92
93
93
94 def get_repo_slug(request):
94 def get_repo_slug(request):
95 return request.environ['pylons.routes_dict'].get('repo_name')
95 _repo = request.environ['pylons.routes_dict'].get('repo_name')
96 if _repo:
97 _repo = _repo.rstrip('/')
98 return _repo
96
99
97
100
98 def get_repos_group_slug(request):
101 def get_repos_group_slug(request):
99 return request.environ['pylons.routes_dict'].get('group_name')
102 _group = request.environ['pylons.routes_dict'].get('group_name')
103 if _group:
104 _group = _group.rstrip('/')
105 return _group
100
106
101
107
102 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
108 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
@@ -44,6 +44,7 b' from rhodecode.lib.compat import json'
44 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.caching_query import FromCache
45
45
46 from rhodecode.model.meta import Base, Session
46 from rhodecode.model.meta import Base, Session
47 import hashlib
47
48
48
49
49 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
@@ -52,6 +53,8 b' log = logging.getLogger(__name__)'
52 # BASE CLASSES
53 # BASE CLASSES
53 #==============================================================================
54 #==============================================================================
54
55
56 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
57
55
58
56 class ModelSerializer(json.JSONEncoder):
59 class ModelSerializer(json.JSONEncoder):
57 """
60 """
@@ -337,8 +340,11 b' class User(Base, BaseModel):'
337 q = cls.query().filter(cls.username == username)
340 q = cls.query().filter(cls.username == username)
338
341
339 if cache:
342 if cache:
340 q = q.options(FromCache("sql_cache_short",
343 q = q.options(FromCache(
341 "get_user_%s" % username))
344 "sql_cache_short",
345 "get_user_%s" % _hash_key(username)
346 )
347 )
342 return q.scalar()
348 return q.scalar()
343
349
344 @classmethod
350 @classmethod
@@ -406,6 +412,7 b' class UsersGroup(Base, BaseModel):'
406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
412 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407
413
408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
414 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
415 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
409
416
410 def __repr__(self):
417 def __repr__(self):
411 return '<userGroup(%s)>' % (self.users_group_name)
418 return '<userGroup(%s)>' % (self.users_group_name)
@@ -418,8 +425,11 b' class UsersGroup(Base, BaseModel):'
418 else:
425 else:
419 q = cls.query().filter(cls.users_group_name == group_name)
426 q = cls.query().filter(cls.users_group_name == group_name)
420 if cache:
427 if cache:
421 q = q.options(FromCache("sql_cache_short",
428 q = q.options(FromCache(
422 "get_user_%s" % group_name))
429 "sql_cache_short",
430 "get_user_%s" % _hash_key(group_name)
431 )
432 )
423 return q.scalar()
433 return q.scalar()
424
434
425 @classmethod
435 @classmethod
@@ -748,8 +758,11 b' class RepoGroup(Base, BaseModel):'
748 gr = cls.query()\
758 gr = cls.query()\
749 .filter(cls.group_name == group_name)
759 .filter(cls.group_name == group_name)
750 if cache:
760 if cache:
751 gr = gr.options(FromCache("sql_cache_short",
761 gr = gr.options(FromCache(
752 "get_group_%s" % group_name))
762 "sql_cache_short",
763 "get_group_%s" % _hash_key(group_name)
764 )
765 )
753 return gr.scalar()
766 return gr.scalar()
754
767
755 @property
768 @property
@@ -345,32 +345,46 b' def SlugifyName():'
345
345
346
346
347 def ValidCloneUri():
347 def ValidCloneUri():
348 from rhodecode.lib.utils import make_ui
349
350 def url_handler(repo_type, url, proto, ui=None):
351 if repo_type == 'hg':
348 from mercurial.httprepo import httprepository, httpsrepository
352 from mercurial.httprepo import httprepository, httpsrepository
349 from rhodecode.lib.utils import make_ui
353 if proto == 'https':
354 httpsrepository(make_ui('db'), url).capabilities
355 elif proto == 'http':
356 httprepository(make_ui('db'), url).capabilities
357 elif repo_type == 'git':
358 #TODO: write a git url validator
359 pass
350
360
351 class _ValidCloneUri(formencode.validators.FancyValidator):
361 class _ValidCloneUri(formencode.validators.FancyValidator):
352
362
353 def to_python(self, value, state):
363 def to_python(self, value, state):
354 if not value:
364
365 repo_type = value.get('repo_type')
366 url = value.get('clone_uri')
367 e_dict = {'clone_uri': _('invalid clone url')}
368
369 if not url:
355 pass
370 pass
356 elif value.startswith('https'):
371 elif url.startswith('https'):
357 try:
372 try:
358 httpsrepository(make_ui('db'), value).capabilities
373 url_handler(repo_type, url, 'https', make_ui('db'))
359 except Exception:
374 except Exception:
360 log.error(traceback.format_exc())
375 log.error(traceback.format_exc())
361 raise formencode.Invalid(_('invalid clone url'), value,
376 raise formencode.Invalid('', value, state, error_dict=e_dict)
362 state)
377 elif url.startswith('http'):
363 elif value.startswith('http'):
364 try:
378 try:
365 httprepository(make_ui('db'), value).capabilities
379 url_handler(repo_type, url, 'http', make_ui('db'))
366 except Exception:
380 except Exception:
367 log.error(traceback.format_exc())
381 log.error(traceback.format_exc())
368 raise formencode.Invalid(_('invalid clone url'), value,
382 raise formencode.Invalid('', value, state, error_dict=e_dict)
369 state)
370 else:
383 else:
371 raise formencode.Invalid(_('Invalid clone url, provide a '
384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
372 'valid clone http\s url'), value,
385 'valid clone http\s url')}
373 state)
386 raise formencode.Invalid('', value, state, error_dict=e_dict)
387
374 return value
388 return value
375
389
376 return _ValidCloneUri
390 return _ValidCloneUri
@@ -645,8 +659,7 b' def RepoForm(edit=False, old_data={}, su'
645 filter_extra_fields = False
659 filter_extra_fields = False
646 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
647 SlugifyName())
661 SlugifyName())
648 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
649 ValidCloneUri()())
650 repo_group = OneOf(repo_groups, hideList=True)
663 repo_group = OneOf(repo_groups, hideList=True)
651 repo_type = OneOf(supported_backends)
664 repo_type = OneOf(supported_backends)
652 description = UnicodeString(strip=True, min=1, not_empty=True)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
@@ -658,7 +671,9 b' def RepoForm(edit=False, old_data={}, su'
658 #this is repo owner
671 #this is repo owner
659 user = All(UnicodeString(not_empty=True), ValidRepoUser)
672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
660
673
661 chained_validators = [ValidRepoName(edit, old_data), ValidPerms()]
674 chained_validators = [ValidCloneUri()(),
675 ValidRepoName(edit, old_data),
676 ValidPerms()]
662 return _RepoForm
677 return _RepoForm
663
678
664
679
@@ -187,20 +187,20 b' class ReposGroupModel(BaseModel):'
187 # change properties
187 # change properties
188 repos_group.group_description = form_data['group_description']
188 repos_group.group_description = form_data['group_description']
189 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
189 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
190 repos_group.group_parent_id = form_data['group_parent_id']
190 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
191 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
191
192 new_path = repos_group.full_path
192 new_path = repos_group.full_path
193
193
194 self.sa.add(repos_group)
194 self.sa.add(repos_group)
195
195
196 self.__rename_group(old_path, new_path)
197
198 # we need to get all repositories from this new group and
196 # we need to get all repositories from this new group and
199 # rename them accordingly to new group path
197 # rename them accordingly to new group path
200 for r in repos_group.repositories:
198 for r in repos_group.repositories:
201 r.repo_name = r.get_new_name(r.just_name)
199 r.repo_name = r.get_new_name(r.just_name)
202 self.sa.add(r)
200 self.sa.add(r)
203
201
202 self.__rename_group(old_path, new_path)
203
204 return repos_group
204 return repos_group
205 except:
205 except:
206 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
@@ -1,8 +1,9 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.db import UsersGroup
2 from rhodecode.model.db import UsersGroup, UsersGroupToPerm, Permission
3
3
4 TEST_USERS_GROUP = 'admins_test'
4 TEST_USERS_GROUP = 'admins_test'
5
5
6
6 class TestAdminUsersGroupsController(TestController):
7 class TestAdminUsersGroupsController(TestController):
7
8
8 def test_index(self):
9 def test_index(self):
@@ -47,7 +48,6 b' class TestAdminUsersGroupsController(Tes'
47 self.checkSessionFlash(response,
48 self.checkSessionFlash(response,
48 'created users group %s' % users_group_name)
49 'created users group %s' % users_group_name)
49
50
50
51 gr = self.Session.query(UsersGroup)\
51 gr = self.Session.query(UsersGroup)\
52 .filter(UsersGroup.users_group_name ==
52 .filter(UsersGroup.users_group_name ==
53 users_group_name).one()
53 users_group_name).one()
@@ -60,6 +60,53 b' class TestAdminUsersGroupsController(Tes'
60
60
61 self.assertEqual(gr, None)
61 self.assertEqual(gr, None)
62
62
63 def test_enable_repository_read_on_group(self):
64 self.log_user()
65 users_group_name = TEST_USERS_GROUP + 'another2'
66 response = self.app.post(url('users_groups'),
67 {'users_group_name': users_group_name,
68 'active':True})
69 response.follow()
70
71 ug = UsersGroup.get_by_group_name(users_group_name)
72 self.checkSessionFlash(response,
73 'created users group %s' % users_group_name)
74
75 response = self.app.put(url('users_group_perm', id=ug.users_group_id),
76 {'create_repo_perm': True})
77
78 response.follow()
79 ug = UsersGroup.get_by_group_name(users_group_name)
80 p = Permission.get_by_key('hg.create.repository')
81 # check if user has this perm
82 perms = UsersGroupToPerm.query()\
83 .filter(UsersGroupToPerm.users_group == ug).all()
84 perms = [[x.__dict__['users_group_id'],
85 x.__dict__['permission_id'],] for x in perms]
86 self.assertEqual(
87 perms,
88 [[ug.users_group_id, p.permission_id]]
89 )
90
91 # DELETE !
92 ug = UsersGroup.get_by_group_name(users_group_name)
93 ugid = ug.users_group_id
94 response = self.app.delete(url('users_group', id=ug.users_group_id))
95 response = response.follow()
96 gr = self.Session.query(UsersGroup)\
97 .filter(UsersGroup.users_group_name ==
98 users_group_name).scalar()
99
100 self.assertEqual(gr, None)
101 p = Permission.get_by_key('hg.create.repository')
102 perms = UsersGroupToPerm.query()\
103 .filter(UsersGroupToPerm.users_group_id == ugid).all()
104 perms = [[x.__dict__['users_group_id'],
105 x.__dict__['permission_id'],] for x in perms]
106 self.assertEqual(
107 perms,
108 []
109 )
63
110
64 def test_delete_browser_fakeout(self):
111 def test_delete_browser_fakeout(self):
65 response = self.app.post(url('users_group', id=1),
112 response = self.app.post(url('users_group', id=1),
@@ -23,7 +23,6 b" def _make_group(path, desc='desc', paren"
23 return gr
23 return gr
24
24
25 gr = ReposGroupModel().create(path, desc, parent_id)
25 gr = ReposGroupModel().create(path, desc, parent_id)
26 Session.commit()
27 return gr
26 return gr
28
27
29
28
@@ -31,13 +30,19 b' class TestReposGroups(unittest.TestCase)'
31
30
32 def setUp(self):
31 def setUp(self):
33 self.g1 = _make_group('test1', skip_if_exists=True)
32 self.g1 = _make_group('test1', skip_if_exists=True)
33 Session.commit()
34 self.g2 = _make_group('test2', skip_if_exists=True)
34 self.g2 = _make_group('test2', skip_if_exists=True)
35 Session.commit()
35 self.g3 = _make_group('test3', skip_if_exists=True)
36 self.g3 = _make_group('test3', skip_if_exists=True)
37 Session.commit()
36
38
37 def tearDown(self):
39 def tearDown(self):
38 print 'out'
40 print 'out'
39
41
40 def __check_path(self, *path):
42 def __check_path(self, *path):
43 """
44 Checks the path for existance !
45 """
41 path = [TESTS_TMP_PATH] + list(path)
46 path = [TESTS_TMP_PATH] + list(path)
42 path = os.path.join(*path)
47 path = os.path.join(*path)
43 return os.path.isdir(path)
48 return os.path.isdir(path)
@@ -49,12 +54,13 b' class TestReposGroups(unittest.TestCase)'
49 ReposGroupModel().delete(id_)
54 ReposGroupModel().delete(id_)
50
55
51 def __update_group(self, id_, path, desc='desc', parent_id=None):
56 def __update_group(self, id_, path, desc='desc', parent_id=None):
52 form_data = dict(group_name=path,
57 form_data = dict(
58 group_name=path,
53 group_description=desc,
59 group_description=desc,
54 group_parent_id=parent_id,
60 group_parent_id=parent_id,
55 perms_updates=[],
61 perms_updates=[],
56 perms_new=[])
62 perms_new=[]
57
63 )
58 gr = ReposGroupModel().update(id_, form_data)
64 gr = ReposGroupModel().update(id_, form_data)
59 return gr
65 return gr
60
66
@@ -150,6 +156,25 b' class TestReposGroups(unittest.TestCase)'
150 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
156 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
151
157
152
158
159 def test_move_to_root(self):
160 g1 = _make_group('t11')
161 Session.commit()
162 g2 = _make_group('t22',parent_id=g1.group_id)
163 Session.commit()
164
165 self.assertEqual(g2.full_path,'t11/t22')
166 self.assertTrue(self.__check_path('t11', 't22'))
167
168 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
169 Session.commit()
170
171 self.assertEqual(g2.group_name,'g22')
172 # we moved out group from t1 to '' so it's full path should be 'g2'
173 self.assertEqual(g2.full_path,'g22')
174 self.assertFalse(self.__check_path('t11', 't22'))
175 self.assertTrue(self.__check_path('g22'))
176
177
153 class TestUser(unittest.TestCase):
178 class TestUser(unittest.TestCase):
154 def __init__(self, methodName='runTest'):
179 def __init__(self, methodName='runTest'):
155 Session.remove()
180 Session.remove()
General Comments 0
You need to be logged in to leave comments. Login now