##// END OF EJS Templates
added new command mappings for mercurial 1.6
marcink -
r330:4c9a295d default
parent child Browse files
Show More
@@ -1,197 +1,198
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # middleware to handle mercurial api calls
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on 2010-04-28
23 23
24 24 @author: marcink
25 25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 26 It's implemented with basic auth function
27 27 """
28 28 from datetime import datetime
29 29 from itertools import chain
30 30 from mercurial.hgweb import hgweb
31 31 from mercurial.hgweb.request import wsgiapplication
32 32 from paste.auth.basic import AuthBasicAuthenticator
33 33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 34 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware
35 35 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
36 36 from pylons_app.model import meta
37 37 from pylons_app.model.db import UserLog, User
38 38 from webob.exc import HTTPNotFound, HTTPForbidden
39 39 import logging
40 40 import os
41 41 import traceback
42 42 log = logging.getLogger(__name__)
43 43
44 44 class SimpleHg(object):
45 45
46 46 def __init__(self, application, config):
47 47 self.application = application
48 48 self.config = config
49 49 #authenticate this mercurial request using
50 50 realm = '%s %s' % (self.config['hg_app_name'], 'mercurial repository')
51 51 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
52 52
53 53 def __call__(self, environ, start_response):
54 54 if not is_mercurial(environ):
55 55 return self.application(environ, start_response)
56 56 else:
57 57 #===================================================================
58 58 # AUTHENTICATE THIS MERCURIAL REQUEST
59 59 #===================================================================
60 60 username = REMOTE_USER(environ)
61 61 if not username:
62 62 result = self.authenticate(environ)
63 63 if isinstance(result, str):
64 64 AUTH_TYPE.update(environ, 'basic')
65 65 REMOTE_USER.update(environ, result)
66 66 else:
67 67 return result.wsgi_application(environ, start_response)
68 68
69 69 try:
70 70 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
71 71 except Exception as e:
72 72 log.error(traceback.format_exc())
73 73 return HTTPNotFound()(environ, start_response)
74 74
75 75 #===================================================================
76 76 # CHECK PERMISSIONS FOR THIS REQUEST
77 77 #===================================================================
78 78 action = self.__get_action(environ)
79 79 if action:
80 80 username = self.__get_environ_user(environ)
81 81 try:
82 82 sa = meta.Session
83 83 user = sa.query(User)\
84 84 .filter(User.username == username).one()
85 85 except:
86 86 return HTTPNotFound()(environ, start_response)
87 87 #check permissions for this repository
88 88 if action == 'pull':
89 89 if not HasPermissionAnyMiddleware('repository.read',
90 90 'repository.write',
91 91 'repository.admin')\
92 92 (user, repo_name):
93 93 return HTTPForbidden()(environ, start_response)
94 94 if action == 'push':
95 95 if not HasPermissionAnyMiddleware('repository.write',
96 96 'repository.admin')\
97 97 (user, repo_name):
98 98 return HTTPForbidden()(environ, start_response)
99 99
100 100 #log action
101 101 self.__log_user_action(user, action, repo_name)
102 102
103 103 #===================================================================
104 104 # MERCURIAL REQUEST HANDLING
105 105 #===================================================================
106 106 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
107 107 self.baseui = make_ui(self.config['hg_app_repo_conf'])
108 108 self.basepath = self.config['base_path']
109 109 self.repo_path = os.path.join(self.basepath, repo_name)
110 110 try:
111 111 app = wsgiapplication(self.__make_app)
112 112 except Exception:
113 113 log.error(traceback.format_exc())
114 114 return HTTPNotFound()(environ, start_response)
115 115
116 116
117 117 #invalidate cache on push
118 118 if action == 'push':
119 119 self.__invalidate_cache(repo_name)
120 120
121 121 messages = ['thanks for using hg app !']
122 122 return self.msg_wrapper(app, environ, start_response, messages)
123 123
124 124
125 125 def msg_wrapper(self, app, environ, start_response, messages):
126 126 """
127 127 Wrapper for custom messages that come out of mercurial respond messages
128 128 is a list of messages that the user will see at the end of response
129 129 from merurial protocol actions that involves remote answers
130 130 @param app:
131 131 @param environ:
132 132 @param start_response:
133 133 """
134 134 def custom_messages(msg_list):
135 135 for msg in msg_list:
136 136 yield msg + '\n'
137 137 org_response = app(environ, start_response)
138 138 return chain(org_response, custom_messages(messages))
139 139
140 140 def __make_app(self):
141 141 hgserve = hgweb(self.repo_path)
142 142 return self.__load_web_settings(hgserve)
143 143
144 144 def __get_environ_user(self, environ):
145 145 return environ.get('REMOTE_USER')
146 146
147 147 def __get_action(self, environ):
148 148 """
149 149 Maps mercurial request commands into a pull or push command.
150 150 @param environ:
151 151 """
152 mapping = {
153 'changegroup': 'pull',
154 'changegroupsubset': 'pull',
155 'unbundle': 'push',
156 'stream_out': 'pull',
157 }
152 mapping = {'changegroup': 'pull',
153 'changegroupsubset': 'pull',
154 'stream_out': 'pull',
155 'listkeys': 'pull',
156 'unbundle': 'push',
157 'pushkey': 'push', }
158
158 159 for qry in environ['QUERY_STRING'].split('&'):
159 160 if qry.startswith('cmd'):
160 161 cmd = qry.split('=')[-1]
161 162 if mapping.has_key(cmd):
162 163 return mapping[cmd]
163 164
164 165 def __log_user_action(self, user, action, repo):
165 166 sa = meta.Session
166 167 try:
167 168 user_log = UserLog()
168 169 user_log.user_id = user.user_id
169 170 user_log.action = action
170 171 user_log.repository = repo.replace('/', '')
171 172 user_log.action_date = datetime.now()
172 173 sa.add(user_log)
173 174 sa.commit()
174 175 log.info('Adding user %s, action %s on %s',
175 176 user.username, action, repo)
176 177 except Exception as e:
177 178 sa.rollback()
178 179 log.error('could not log user action:%s', str(e))
179 180
180 181 def __invalidate_cache(self, repo_name):
181 182 """we know that some change was made to repositories and we should
182 183 invalidate the cache to see the changes right away but only for
183 184 push requests"""
184 185 invalidate_cache('cached_repo_list')
185 186 invalidate_cache('full_changelog', repo_name)
186 187
187 188
188 189 def __load_web_settings(self, hgserve):
189 190 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
190 191 #set the global ui for hgserve
191 192 hgserve.repo.ui = self.baseui
192 193
193 194 if repoui:
194 195 #set the repository based config
195 196 hgserve.repo.ui = repoui
196 197
197 198 return hgserve
General Comments 0
You need to be logged in to leave comments. Login now