##// END OF EJS Templates
Rewrite simehg for enabling cloning with raw url for anonymous access + some optimizations for making less queries when authenticating users....
marcink -
r910:811fa5d4 beta
parent child Browse files
Show More
@@ -1,144 +1,147
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import calendar
28 import calendar
29 import logging
29 import logging
30 from time import mktime
30 from time import mktime
31 from datetime import datetime, timedelta
31 from datetime import datetime, timedelta
32
32
33 from vcs.exceptions import ChangesetError
33 from vcs.exceptions import ChangesetError
34
34
35 from pylons import tmpl_context as c, request, url
35 from pylons import tmpl_context as c, request, url
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.db import Statistics
39 from rhodecode.model.db import Statistics
40
40
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
44
44
45 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib import run_task
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
47
47
48 from webhelpers.paginate import Page
48 from webhelpers.paginate import Page
49
49
50 try:
50 try:
51 import json
51 import json
52 except ImportError:
52 except ImportError:
53 #python 2.5 compatibility
53 #python 2.5 compatibility
54 import simplejson as json
54 import simplejson as json
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 class SummaryController(BaseController):
57 class SummaryController(BaseController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(SummaryController, self).__before__()
63 super(SummaryController, self).__before__()
64
64
65 def index(self):
65 def index(self):
66 scm_model = ScmModel()
66 scm_model = ScmModel()
67 c.repo_info = scm_model.get_repo(c.repo_name)
67 c.repo_info = scm_model.get_repo(c.repo_name)
68 c.following = scm_model.is_following_repo(c.repo_name,
68 c.following = scm_model.is_following_repo(c.repo_name,
69 c.rhodecode_user.user_id)
69 c.rhodecode_user.user_id)
70 def url_generator(**kw):
70 def url_generator(**kw):
71 return url('shortlog_home', repo_name=c.repo_name, **kw)
71 return url('shortlog_home', repo_name=c.repo_name, **kw)
72
72
73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
74 url=url_generator)
74 url=url_generator)
75
75
76 e = request.environ
76 e = request.environ
77
77
78 if self.rhodecode_user.username == 'default':
78 if self.rhodecode_user.username == 'default':
79 password = ':default'
79 #for default(anonymous) user we don't need to pass credentials
80 username = ''
81 password = ''
80 else:
82 else:
83 username = str(c.rhodecode_user.username)
81 password = ''
84 password = ''
82
85
83 uri = u'%(protocol)s://%(user)s%(password)s@%(host)s%(prefix)s/%(repo_name)s' % {
86 uri = u'%(protocol)s://%(user)s%(password)s@%(host)s%(prefix)s/%(repo_name)s' % {
84 'protocol': e.get('wsgi.url_scheme'),
87 'protocol': e.get('wsgi.url_scheme'),
85 'user':str(c.rhodecode_user.username),
88 'user':username,
86 'password':password,
89 'password':password,
87 'host':e.get('HTTP_HOST'),
90 'host':e.get('HTTP_HOST'),
88 'prefix':e.get('SCRIPT_NAME'),
91 'prefix':e.get('SCRIPT_NAME'),
89 'repo_name':c.repo_name, }
92 'repo_name':c.repo_name, }
90 c.clone_repo_url = uri
93 c.clone_repo_url = uri
91 c.repo_tags = OrderedDict()
94 c.repo_tags = OrderedDict()
92 for name, hash in c.repo_info.tags.items()[:10]:
95 for name, hash in c.repo_info.tags.items()[:10]:
93 try:
96 try:
94 c.repo_tags[name] = c.repo_info.get_changeset(hash)
97 c.repo_tags[name] = c.repo_info.get_changeset(hash)
95 except ChangesetError:
98 except ChangesetError:
96 c.repo_tags[name] = EmptyChangeset(hash)
99 c.repo_tags[name] = EmptyChangeset(hash)
97
100
98 c.repo_branches = OrderedDict()
101 c.repo_branches = OrderedDict()
99 for name, hash in c.repo_info.branches.items()[:10]:
102 for name, hash in c.repo_info.branches.items()[:10]:
100 try:
103 try:
101 c.repo_branches[name] = c.repo_info.get_changeset(hash)
104 c.repo_branches[name] = c.repo_info.get_changeset(hash)
102 except ChangesetError:
105 except ChangesetError:
103 c.repo_branches[name] = EmptyChangeset(hash)
106 c.repo_branches[name] = EmptyChangeset(hash)
104
107
105 td = datetime.today() + timedelta(days=1)
108 td = datetime.today() + timedelta(days=1)
106 y, m, d = td.year, td.month, td.day
109 y, m, d = td.year, td.month, td.day
107
110
108 ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month,
111 ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month,
109 d, 0, 0, 0, 0, 0, 0,))
112 d, 0, 0, 0, 0, 0, 0,))
110 ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
113 ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
111 d, 0, 0, 0, 0, 0, 0,))
114 d, 0, 0, 0, 0, 0, 0,))
112
115
113 ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
116 ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
114 if c.repo_info.dbrepo.enable_statistics:
117 if c.repo_info.dbrepo.enable_statistics:
115 c.no_data_msg = _('No data loaded yet')
118 c.no_data_msg = _('No data loaded yet')
116 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
119 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
117 else:
120 else:
118 c.no_data_msg = _('Statistics update are disabled for this repository')
121 c.no_data_msg = _('Statistics update are disabled for this repository')
119 c.ts_min = ts_min_m
122 c.ts_min = ts_min_m
120 c.ts_max = ts_max_y
123 c.ts_max = ts_max_y
121
124
122 stats = self.sa.query(Statistics)\
125 stats = self.sa.query(Statistics)\
123 .filter(Statistics.repository == c.repo_info.dbrepo)\
126 .filter(Statistics.repository == c.repo_info.dbrepo)\
124 .scalar()
127 .scalar()
125
128
126
129
127 if stats and stats.languages:
130 if stats and stats.languages:
128 c.no_data = False is c.repo_info.dbrepo.enable_statistics
131 c.no_data = False is c.repo_info.dbrepo.enable_statistics
129 lang_stats = json.loads(stats.languages)
132 lang_stats = json.loads(stats.languages)
130 c.commit_data = stats.commit_activity
133 c.commit_data = stats.commit_activity
131 c.overview_data = stats.commit_activity_combined
134 c.overview_data = stats.commit_activity_combined
132 c.trending_languages = json.dumps(OrderedDict(
135 c.trending_languages = json.dumps(OrderedDict(
133 sorted(lang_stats.items(), reverse=True,
136 sorted(lang_stats.items(), reverse=True,
134 key=lambda k: k[1])[:10]
137 key=lambda k: k[1])[:10]
135 )
138 )
136 )
139 )
137 else:
140 else:
138 c.commit_data = json.dumps({})
141 c.commit_data = json.dumps({})
139 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
140 c.trending_languages = json.dumps({})
143 c.trending_languages = json.dumps({})
141 c.no_data = True
144 c.no_data = True
142
145
143 return render('summary/summary.html')
146 return render('summary/summary.html')
144
147
@@ -1,216 +1,275
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software; you can redistribute it and/or
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
17 # of the License or (at your opinion) any later version of the license.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
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, write to the Free Software
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
27 # MA 02110-1301, USA.
28
28
29 import os
30 import logging
31 import traceback
32
29 from mercurial.error import RepoError
33 from mercurial.error import RepoError
30 from mercurial.hgweb import hgweb
34 from mercurial.hgweb import hgweb
31 from mercurial.hgweb.request import wsgiapplication
35 from mercurial.hgweb.request import wsgiapplication
36
32 from paste.auth.basic import AuthBasicAuthenticator
37 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
38 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
39
34 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
40 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
35 from rhodecode.lib.utils import make_ui, invalidate_cache, \
41 from rhodecode.lib.utils import make_ui, invalidate_cache, \
36 check_repo_fast, ui_sections
42 check_repo_fast, ui_sections
37 from rhodecode.model.user import UserModel
43 from rhodecode.model.user import UserModel
44
38 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
45 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
39 import logging
40 import os
41 import traceback
42
46
43 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
44
48
45 def is_mercurial(environ):
49 def is_mercurial(environ):
46 """Returns True if request's target is mercurial server - header
50 """Returns True if request's target is mercurial server - header
47 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
48 """
52 """
49 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
50 if http_accept and http_accept.startswith('application/mercurial'):
54 if http_accept and http_accept.startswith('application/mercurial'):
51 return True
55 return True
52 return False
56 return False
53
57
54 class SimpleHg(object):
58 class SimpleHg(object):
55
59
56 def __init__(self, application, config):
60 def __init__(self, application, config):
57 self.application = application
61 self.application = application
58 self.config = config
62 self.config = config
59 #authenticate this mercurial request using authfunc
63 #authenticate this mercurial request using authfunc
60 self.authenticate = AuthBasicAuthenticator('', authfunc)
64 self.authenticate = AuthBasicAuthenticator('', authfunc)
61 self.ipaddr = '0.0.0.0'
65 self.ipaddr = '0.0.0.0'
62 self.repository = None
66 self.repo_name = None
63 self.username = None
67 self.username = None
64 self.action = None
68 self.action = None
65
69
66 def __call__(self, environ, start_response):
70 def __call__(self, environ, start_response):
67 if not is_mercurial(environ):
71 if not is_mercurial(environ):
68 return self.application(environ, start_response)
72 return self.application(environ, start_response)
69
73
70 proxy_key = 'HTTP_X_REAL_IP'
74 proxy_key = 'HTTP_X_REAL_IP'
71 def_key = 'REMOTE_ADDR'
75 def_key = 'REMOTE_ADDR'
72 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
73 # skip passing error to error controller
77 # skip passing error to error controller
74 environ['pylons.status_code_redirect'] = True
78 environ['pylons.status_code_redirect'] = True
75 #===================================================================
79
76 # AUTHENTICATE THIS MERCURIAL REQUEST
80 #======================================================================
77 #===================================================================
81 # GET ACTION PULL or PUSH
78 username = REMOTE_USER(environ)
82 #======================================================================
83 self.action = self.__get_action(environ)
84 try:
85 #==================================================================
86 # GET REPOSITORY NAME
87 #==================================================================
88 self.repo_name = self.__get_repository(environ)
89 except:
90 return HTTPInternalServerError()(environ, start_response)
79
91
80 if not username:
92 #======================================================================
93 # CHECK ANONYMOUS PERMISSION
94 #======================================================================
95 if self.action in ['pull', 'push']:
96 anonymous_user = self.__get_user('default')
97 self.username = anonymous_user.username
98 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
99 self.repo_name)
100
101 if anonymous_perm is not True or anonymous_user.active is False:
102 if anonymous_perm is not True:
103 log.debug('Not enough credentials to access this repository'
104 'as anonymous user')
105 if anonymous_user.active is False:
106 log.debug('Anonymous access is disabled, running '
107 'authentication')
108 #==================================================================
109 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE NEED
110 # TO AUTHENTICATE AND ASK FOR AUTHENTICATED USER PERMISSIONS
111 #==================================================================
112
113 if not REMOTE_USER(environ):
81 self.authenticate.realm = self.config['rhodecode_realm']
114 self.authenticate.realm = self.config['rhodecode_realm']
82 result = self.authenticate(environ)
115 result = self.authenticate(environ)
83 if isinstance(result, str):
116 if isinstance(result, str):
84 AUTH_TYPE.update(environ, 'basic')
117 AUTH_TYPE.update(environ, 'basic')
85 REMOTE_USER.update(environ, result)
118 REMOTE_USER.update(environ, result)
86 else:
119 else:
87 return result.wsgi_application(environ, start_response)
120 return result.wsgi_application(environ, start_response)
88
121
89 #=======================================================================
90 # GET REPOSITORY
91 #=======================================================================
92 try:
93 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
94 if repo_name.endswith('/'):
95 repo_name = repo_name.rstrip('/')
96 self.repository = repo_name
97 except:
98 log.error(traceback.format_exc())
99 return HTTPInternalServerError()(environ, start_response)
100
122
101 #===================================================================
123 #==================================================================
102 # CHECK PERMISSIONS FOR THIS REQUEST
124 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
103 #===================================================================
125 # BASIC AUTH
104 self.action = self.__get_action(environ)
126 #==================================================================
105 if self.action:
127
128 if self.action in ['pull', 'push']:
106 username = self.__get_environ_user(environ)
129 username = self.__get_environ_user(environ)
107 try:
130 try:
108 user = self.__get_user(username)
131 user = self.__get_user(username)
109 self.username = user.username
132 self.username = user.username
110 except:
133 except:
111 log.error(traceback.format_exc())
134 log.error(traceback.format_exc())
112 return HTTPInternalServerError()(environ, start_response)
135 return HTTPInternalServerError()(environ, start_response)
113
136
114 #check permissions for this repository
137 #check permissions for this repository
115 if self.action == 'push':
138 perm = self.__check_permission(self.action, user, self.repo_name)
116 if not HasPermissionAnyMiddleware('repository.write',
139 if perm is not True:
117 'repository.admin')\
118 (user, repo_name):
119 return HTTPForbidden()(environ, start_response)
120
121 else:
122 #any other action need at least read permission
123 if not HasPermissionAnyMiddleware('repository.read',
124 'repository.write',
125 'repository.admin')\
126 (user, repo_name):
127 return HTTPForbidden()(environ, start_response)
140 return HTTPForbidden()(environ, start_response)
128
141
129 self.extras = {'ip':self.ipaddr,
142 self.extras = {'ip':self.ipaddr,
130 'username':self.username,
143 'username':self.username,
131 'action':self.action,
144 'action':self.action,
132 'repository':self.repository}
145 'repository':self.repo_name}
133
146
134 #===================================================================
147 #===================================================================
135 # MERCURIAL REQUEST HANDLING
148 # MERCURIAL REQUEST HANDLING
136 #===================================================================
149 #===================================================================
137 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
150 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
138 self.baseui = make_ui('db')
151 self.baseui = make_ui('db')
139 self.basepath = self.config['base_path']
152 self.basepath = self.config['base_path']
140 self.repo_path = os.path.join(self.basepath, repo_name)
153 self.repo_path = os.path.join(self.basepath, self.repo_name)
141
154
142 #quick check if that dir exists...
155 #quick check if that dir exists...
143 if check_repo_fast(repo_name, self.basepath):
156 if check_repo_fast(self.repo_name, self.basepath):
144 return HTTPNotFound()(environ, start_response)
157 return HTTPNotFound()(environ, start_response)
145 try:
158 try:
146 app = wsgiapplication(self.__make_app)
159 app = wsgiapplication(self.__make_app)
147 except RepoError, e:
160 except RepoError, e:
148 if str(e).find('not found') != -1:
161 if str(e).find('not found') != -1:
149 return HTTPNotFound()(environ, start_response)
162 return HTTPNotFound()(environ, start_response)
150 except Exception:
163 except Exception:
151 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
152 return HTTPInternalServerError()(environ, start_response)
165 return HTTPInternalServerError()(environ, start_response)
153
166
154 #invalidate cache on push
167 #invalidate cache on push
155 if self.action == 'push':
168 if self.action == 'push':
156 self.__invalidate_cache(repo_name)
169 self.__invalidate_cache(self.repo_name)
157
170
158 return app(environ, start_response)
171 return app(environ, start_response)
159
172
160
173
161 def __make_app(self):
174 def __make_app(self):
175 """Make an wsgi application using hgweb, and my generated baseui
176 instance
177 """
178
162 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
179 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
163 return self.__load_web_settings(hgserve, self.extras)
180 return self.__load_web_settings(hgserve, self.extras)
164
181
182
183 def __check_permission(self, action, user, repo_name):
184 """Checks permissions using action (push/pull) user and repository
185 name
186
187 :param action: push or pull action
188 :param user: user instance
189 :param repo_name: repository name
190 """
191 if action == 'push':
192 if not HasPermissionAnyMiddleware('repository.write',
193 'repository.admin')\
194 (user, repo_name):
195 return False
196
197 else:
198 #any other action need at least read permission
199 if not HasPermissionAnyMiddleware('repository.read',
200 'repository.write',
201 'repository.admin')\
202 (user, repo_name):
203 return False
204
205 return True
206
207
208 def __get_repository(self, environ):
209 """Get's repository name out of PATH_INFO header
210
211 :param environ: environ where PATH_INFO is stored
212 """
213 try:
214 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
215 if repo_name.endswith('/'):
216 repo_name = repo_name.rstrip('/')
217 except:
218 log.error(traceback.format_exc())
219 raise
220
221 return repo_name
222
165 def __get_environ_user(self, environ):
223 def __get_environ_user(self, environ):
166 return environ.get('REMOTE_USER')
224 return environ.get('REMOTE_USER')
167
225
168 def __get_user(self, username):
226 def __get_user(self, username):
169 return UserModel().get_by_username(username, cache=True)
227 return UserModel().get_by_username(username, cache=True)
170
228
171 def __get_action(self, environ):
229 def __get_action(self, environ):
172 """Maps mercurial request commands into a clone,pull or push command.
230 """Maps mercurial request commands into a clone,pull or push command.
173 This should always return a valid command string
231 This should always return a valid command string
232
174 :param environ:
233 :param environ:
175 """
234 """
176 mapping = {'changegroup': 'pull',
235 mapping = {'changegroup': 'pull',
177 'changegroupsubset': 'pull',
236 'changegroupsubset': 'pull',
178 'stream_out': 'pull',
237 'stream_out': 'pull',
179 'listkeys': 'pull',
238 'listkeys': 'pull',
180 'unbundle': 'push',
239 'unbundle': 'push',
181 'pushkey': 'push', }
240 'pushkey': 'push', }
182 for qry in environ['QUERY_STRING'].split('&'):
241 for qry in environ['QUERY_STRING'].split('&'):
183 if qry.startswith('cmd'):
242 if qry.startswith('cmd'):
184 cmd = qry.split('=')[-1]
243 cmd = qry.split('=')[-1]
185 if mapping.has_key(cmd):
244 if mapping.has_key(cmd):
186 return mapping[cmd]
245 return mapping[cmd]
187 else:
246 else:
188 return cmd
247 return cmd
189
248
190 def __invalidate_cache(self, repo_name):
249 def __invalidate_cache(self, repo_name):
191 """we know that some change was made to repositories and we should
250 """we know that some change was made to repositories and we should
192 invalidate the cache to see the changes right away but only for
251 invalidate the cache to see the changes right away but only for
193 push requests"""
252 push requests"""
194 invalidate_cache('get_repo_cached_%s' % repo_name)
253 invalidate_cache('get_repo_cached_%s' % repo_name)
195
254
196
255
197 def __load_web_settings(self, hgserve, extras={}):
256 def __load_web_settings(self, hgserve, extras={}):
198 #set the global ui for hgserve instance passed
257 #set the global ui for hgserve instance passed
199 hgserve.repo.ui = self.baseui
258 hgserve.repo.ui = self.baseui
200
259
201 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
260 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
202
261
203 #inject some additional parameters that will be available in ui
262 #inject some additional parameters that will be available in ui
204 #for hooks
263 #for hooks
205 for k, v in extras.items():
264 for k, v in extras.items():
206 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
265 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
207
266
208 repoui = make_ui('file', hgrc, False)
267 repoui = make_ui('file', hgrc, False)
209
268
210 if repoui:
269 if repoui:
211 #overwrite our ui instance with the section from hgrc file
270 #overwrite our ui instance with the section from hgrc file
212 for section in ui_sections:
271 for section in ui_sections:
213 for k, v in repoui.configitems(section):
272 for k, v in repoui.configitems(section):
214 hgserve.repo.ui.setconfig(section, k, v)
273 hgserve.repo.ui.setconfig(section, k, v)
215
274
216 return hgserve
275 return hgserve
@@ -1,225 +1,228
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.tests.test_hg_operations
3 rhodecode.tests.test_hg_operations
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Test suite for making push/pull operations
6 Test suite for making push/pull operations
7
7
8 :created_on: Dec 30, 2010
8 :created_on: Dec 30, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
11 """
12
12
13 import os
13 import os
14 import shutil
14 import shutil
15 import logging
15 import logging
16
16
17 from subprocess import Popen, PIPE
17 from subprocess import Popen, PIPE
18
18
19 from os.path import join as jn
19 from os.path import join as jn
20
20
21 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
21 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
22
22
23 USER = 'test_admin'
23 USER = 'test_admin'
24 PASS = 'test12'
24 PASS = 'test12'
25 HOST = '127.0.0.1:5000'
25 HOST = '127.0.0.1:5000'
26
26 DEBUG = True
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29
29
30 class Command(object):
30 class Command(object):
31
31
32 def __init__(self, cwd):
32 def __init__(self, cwd):
33 self.cwd = cwd
33 self.cwd = cwd
34
34
35 def execute(self, cmd, *args):
35 def execute(self, cmd, *args):
36 """Runs command on the system with given ``args``.
36 """Runs command on the system with given ``args``.
37 """
37 """
38
38
39 command = cmd + ' ' + ' '.join(args)
39 command = cmd + ' ' + ' '.join(args)
40 log.debug('Executing %s' % command)
40 log.debug('Executing %s' % command)
41 if DEBUG:
41 print command
42 print command
42 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
43 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
43 stdout, stderr = p.communicate()
44 stdout, stderr = p.communicate()
45 if DEBUG:
44 print stdout, stderr
46 print stdout, stderr
45 return stdout, stderr
47 return stdout, stderr
46
48
47
49
48 #===============================================================================
50 #==============================================================================
49 # TESTS
51 # TESTS
50 #===============================================================================
52 #==============================================================================
51 def test_clone():
53 def test_clone():
52 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
54 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
53
55
54 try:
56 try:
55 shutil.rmtree(path, ignore_errors=True)
57 shutil.rmtree(path, ignore_errors=True)
56 os.makedirs(path)
58 os.makedirs(path)
57 #print 'made dirs %s' % jn(path)
59 #print 'made dirs %s' % jn(path)
58 except OSError:
60 except OSError:
59 raise
61 raise
60
62
61
63
62 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
64 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
63 {'user':USER,
65 {'user':USER,
64 'pass':PASS,
66 'pass':PASS,
65 'host':HOST,
67 'host':HOST,
66 'cloned_repo':HG_REPO,
68 'cloned_repo':HG_REPO,
67 'dest':path}
69 'dest':path}
68
70
69 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
71 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
70
72
71 assert """adding file changes""" in stdout, 'no messages about cloning'
73 assert """adding file changes""" in stdout, 'no messages about cloning'
72 assert """abort""" not in stderr , 'got error from clone'
74 assert """abort""" not in stderr , 'got error from clone'
73
75
74
76
75
77
76 def test_clone_anonymous_ok():
78 def test_clone_anonymous_ok():
77 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
79 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
78
80
79 try:
81 try:
80 shutil.rmtree(path, ignore_errors=True)
82 shutil.rmtree(path, ignore_errors=True)
81 os.makedirs(path)
83 os.makedirs(path)
82 #print 'made dirs %s' % jn(path)
84 #print 'made dirs %s' % jn(path)
83 except OSError:
85 except OSError:
84 raise
86 raise
85
87
86
88
87 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
89 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
88 {'user':USER,
90 {'user':USER,
89 'pass':PASS,
91 'pass':PASS,
90 'host':HOST,
92 'host':HOST,
91 'cloned_repo':HG_REPO,
93 'cloned_repo':HG_REPO,
92 'dest':path}
94 'dest':path}
93
95
94 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
96 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
95 print stdout, stderr
97 print stdout, stderr
96 assert """adding file changes""" in stdout, 'no messages about cloning'
98 assert """adding file changes""" in stdout, 'no messages about cloning'
97 assert """abort""" not in stderr , 'got error from clone'
99 assert """abort""" not in stderr , 'got error from clone'
98
100
99 def test_clone_wrong_credentials():
101 def test_clone_wrong_credentials():
100 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
102 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
101
103
102 try:
104 try:
103 shutil.rmtree(path, ignore_errors=True)
105 shutil.rmtree(path, ignore_errors=True)
104 os.makedirs(path)
106 os.makedirs(path)
105 #print 'made dirs %s' % jn(path)
107 #print 'made dirs %s' % jn(path)
106 except OSError:
108 except OSError:
107 raise
109 raise
108
110
109
111
110 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
112 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
111 {'user':USER + 'error',
113 {'user':USER + 'error',
112 'pass':PASS,
114 'pass':PASS,
113 'host':HOST,
115 'host':HOST,
114 'cloned_repo':HG_REPO,
116 'cloned_repo':HG_REPO,
115 'dest':path}
117 'dest':path}
116
118
117 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
119 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
118
120
119 assert """abort: authorization failed""" in stderr , 'no error from wrong credentials'
121 assert """abort: authorization failed""" in stderr , 'no error from wrong credentials'
120
122
121
123
122 def test_pull():
124 def test_pull():
123 pass
125 pass
124
126
125 def test_push():
127 def test_push():
126
128
127 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
129 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
128 for i in xrange(5):
130 for i in xrange(5):
129 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
131 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
130 Command(cwd).execute(cmd)
132 Command(cwd).execute(cmd)
131
133
132 cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file)
134 cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file)
133 Command(cwd).execute(cmd)
135 Command(cwd).execute(cmd)
134
136
135 Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
137 Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
136
138
137 def test_push_new_file():
139 def test_push_new_file():
138
140
139 test_clone()
141 test_clone()
140
142
141 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
143 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
142 added_file = jn(path, 'setup.py')
144 added_file = jn(path, 'setup.py')
143
145
144 Command(cwd).execute('touch %s' % added_file)
146 Command(cwd).execute('touch %s' % added_file)
145
147
146 Command(cwd).execute('hg add %s' % added_file)
148 Command(cwd).execute('hg add %s' % added_file)
147
149
148 for i in xrange(15):
150 for i in xrange(15):
149 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
151 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
150 Command(cwd).execute(cmd)
152 Command(cwd).execute(cmd)
151
153
152 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
154 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
153 Command(cwd).execute(cmd)
155 Command(cwd).execute(cmd)
154
156
155 push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
157 push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
156 {'user':USER,
158 {'user':USER,
157 'pass':PASS,
159 'pass':PASS,
158 'host':HOST,
160 'host':HOST,
159 'cloned_repo':HG_REPO,
161 'cloned_repo':HG_REPO,
160 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
162 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
161
163
162 Command(cwd).execute('hg push %s' % push_url)
164 Command(cwd).execute('hg push %s' % push_url)
163
165
164 def test_push_wrong_credentials():
166 def test_push_wrong_credentials():
165
167
166 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
168 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
167 {'user':USER + 'xxx',
169 {'user':USER + 'xxx',
168 'pass':PASS,
170 'pass':PASS,
169 'host':HOST,
171 'host':HOST,
170 'cloned_repo':HG_REPO,
172 'cloned_repo':HG_REPO,
171 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
173 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
172
174
173 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
175 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
174 for i in xrange(5):
176 for i in xrange(5):
175 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
177 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
176 Command(cwd).execute(cmd)
178 Command(cwd).execute(cmd)
177
179
178 cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file)
180 cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file)
179 Command(cwd).execute(cmd)
181 Command(cwd).execute(cmd)
180
182
181 Command(cwd).execute('hg push %s' % clone_url)
183 Command(cwd).execute('hg push %s' % clone_url)
182
184
183 def test_push_wrong_path():
185 def test_push_wrong_path():
184 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
186 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
185 added_file = jn(path, 'somefile.py')
187 added_file = jn(path, 'somefile.py')
186
188
187 try:
189 try:
188 shutil.rmtree(path, ignore_errors=True)
190 shutil.rmtree(path, ignore_errors=True)
189 os.makedirs(path)
191 os.makedirs(path)
190 print 'made dirs %s' % jn(path)
192 print 'made dirs %s' % jn(path)
191 except OSError:
193 except OSError:
192 raise
194 raise
193
195
194 Command(cwd).execute("""echo '' > %s""" % added_file)
196 Command(cwd).execute("""echo '' > %s""" % added_file)
195 Command(cwd).execute("""hg init %s""" % path)
197 Command(cwd).execute("""hg init %s""" % path)
196 Command(cwd).execute("""hg add %s""" % added_file)
198 Command(cwd).execute("""hg add %s""" % added_file)
197
199
198 for i in xrange(2):
200 for i in xrange(2):
199 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
201 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
200 Command(cwd).execute(cmd)
202 Command(cwd).execute(cmd)
201
203
202 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
204 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
203 Command(cwd).execute(cmd)
205 Command(cwd).execute(cmd)
204
206
205 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
207 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
206 {'user':USER,
208 {'user':USER,
207 'pass':PASS,
209 'pass':PASS,
208 'host':HOST,
210 'host':HOST,
209 'cloned_repo':HG_REPO + '_error',
211 'cloned_repo':HG_REPO + '_error',
210 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
212 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
211
213
212 stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
214 stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
213 assert """abort: HTTP Error 403: Forbidden""" in stderr
215 assert """abort: HTTP Error 403: Forbidden""" in stderr
214
216
215
217
216 if __name__ == '__main__':
218 if __name__ == '__main__':
217 test_clone()
219 test_clone()
218 test_clone_wrong_credentials()
220
221 #test_clone_wrong_credentials()
219 ##test_clone_anonymous_ok()
222 ##test_clone_anonymous_ok()
220
223 test_pull()
221 #test_push_new_file()
224 #test_push_new_file()
222 #test_push_wrong_path()
225 #test_push_wrong_path()
223 #test_push_wrong_credentials()
226 #test_push_wrong_credentials()
224
227
225
228
General Comments 0
You need to be logged in to leave comments. Login now