##// END OF EJS Templates
Fixed issue with gzipped streams for large git pushes.
marcink -
r2578:d24c70ec beta
parent child Browse files
Show More
@@ -1,303 +1,305 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import re
29 29 import logging
30 30 import traceback
31 31
32 32 from dulwich import server as dulserver
33 from dulwich.web import LimitedInputFilter, GunzipFilter
33 34
34 35
35 36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 37
37 38 def handle(self):
38 39 write = lambda x: self.proto.write_sideband(1, x)
39 40
40 41 graph_walker = dulserver.ProtocolGraphWalker(self,
41 42 self.repo.object_store,
42 43 self.repo.get_peeled)
43 44 objects_iter = self.repo.fetch_objects(
44 45 graph_walker.determine_wants, graph_walker, self.progress,
45 46 get_tagged=self.get_tagged)
46 47
47 48 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 49 # that the client still expects a 0-object pack in most cases.
49 50 if objects_iter is None:
50 51 return
51 52
52 53 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 54 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 55 objects_iter)
55 56 messages = []
56 57 messages.append('thank you for using rhodecode')
57 58
58 59 for msg in messages:
59 60 self.progress(msg + "\n")
60 61 # we are done
61 62 self.proto.write("0000")
62 63
63 64
64 65 dulserver.DEFAULT_HANDLERS = {
65 66 #git-ls-remote, git-clone, git-fetch and git-pull
66 67 'git-upload-pack': SimpleGitUploadPackHandler,
67 68 #git-push
68 69 'git-receive-pack': dulserver.ReceivePackHandler,
69 70 }
70 71
71 72 # not used for now until dulwich get's fixed
72 73 #from dulwich.repo import Repo
73 74 #from dulwich.web import make_wsgi_chain
74 75
75 76 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
76 77
77 78 from rhodecode.lib.utils2 import safe_str
78 79 from rhodecode.lib.base import BaseVCSController
79 80 from rhodecode.lib.auth import get_container_username
80 81 from rhodecode.lib.utils import is_valid_repo, make_ui
81 82 from rhodecode.model.db import User, RhodeCodeUi
82 83
83 84 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
84 85
85 86 log = logging.getLogger(__name__)
86 87
87 88
88 89 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
89 90
90 91
91 92 def is_git(environ):
92 93 path_info = environ['PATH_INFO']
93 94 isgit_path = GIT_PROTO_PAT.match(path_info)
94 95 log.debug('pathinfo: %s detected as GIT %s' % (
95 96 path_info, isgit_path != None)
96 97 )
97 98 return isgit_path
98 99
99 100
100 101 class SimpleGit(BaseVCSController):
101 102
102 103 def _handle_request(self, environ, start_response):
103 104
104 105 if not is_git(environ):
105 106 return self.application(environ, start_response)
106 107
107 108 ipaddr = self._get_ip_addr(environ)
108 109 username = None
109 110 self._git_first_op = False
110 111 # skip passing error to error controller
111 112 environ['pylons.status_code_redirect'] = True
112 113
113 114 #======================================================================
114 115 # EXTRACT REPOSITORY NAME FROM ENV
115 116 #======================================================================
116 117 try:
117 118 repo_name = self.__get_repository(environ)
118 119 log.debug('Extracted repo name is %s' % repo_name)
119 120 except:
120 121 return HTTPInternalServerError()(environ, start_response)
121 122
122 123 # quick check if that dir exists...
123 124 if is_valid_repo(repo_name, self.basepath) is False:
124 125 return HTTPNotFound()(environ, start_response)
125 126
126 127 #======================================================================
127 128 # GET ACTION PULL or PUSH
128 129 #======================================================================
129 130 action = self.__get_action(environ)
130 131
131 132 #======================================================================
132 133 # CHECK ANONYMOUS PERMISSION
133 134 #======================================================================
134 135 if action in ['pull', 'push']:
135 136 anonymous_user = self.__get_user('default')
136 137 username = anonymous_user.username
137 138 anonymous_perm = self._check_permission(action, anonymous_user,
138 139 repo_name)
139 140
140 141 if anonymous_perm is not True or anonymous_user.active is False:
141 142 if anonymous_perm is not True:
142 143 log.debug('Not enough credentials to access this '
143 144 'repository as anonymous user')
144 145 if anonymous_user.active is False:
145 146 log.debug('Anonymous access is disabled, running '
146 147 'authentication')
147 148 #==============================================================
148 149 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
149 150 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
150 151 #==============================================================
151 152
152 153 # Attempting to retrieve username from the container
153 154 username = get_container_username(environ, self.config)
154 155
155 156 # If not authenticated by the container, running basic auth
156 157 if not username:
157 158 self.authenticate.realm = \
158 159 safe_str(self.config['rhodecode_realm'])
159 160 result = self.authenticate(environ)
160 161 if isinstance(result, str):
161 162 AUTH_TYPE.update(environ, 'basic')
162 163 REMOTE_USER.update(environ, result)
163 164 username = result
164 165 else:
165 166 return result.wsgi_application(environ, start_response)
166 167
167 168 #==============================================================
168 169 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
169 170 #==============================================================
170 171 try:
171 172 user = self.__get_user(username)
172 173 if user is None or not user.active:
173 174 return HTTPForbidden()(environ, start_response)
174 175 username = user.username
175 176 except:
176 177 log.error(traceback.format_exc())
177 178 return HTTPInternalServerError()(environ, start_response)
178 179
179 180 #check permissions for this repository
180 181 perm = self._check_permission(action, user, repo_name)
181 182 if perm is not True:
182 183 return HTTPForbidden()(environ, start_response)
183 184
184 185 extras = {
185 186 'ip': ipaddr,
186 187 'username': username,
187 188 'action': action,
188 189 'repository': repo_name,
189 190 'scm': 'git',
190 191 }
191 192
192 193 #===================================================================
193 194 # GIT REQUEST HANDLING
194 195 #===================================================================
195 196 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
196 197 log.debug('Repository path is %s' % repo_path)
197 198
198 199 baseui = make_ui('db')
199 200 self.__inject_extras(repo_path, baseui, extras)
200 201
201 202 try:
202 203 # invalidate cache on push
203 204 if action == 'push':
204 205 self._invalidate_cache(repo_name)
205 206 self._handle_githooks(repo_name, action, baseui, environ)
206 207
207 208 log.info('%s action on GIT repo "%s"' % (action, repo_name))
208 209 app = self.__make_app(repo_name, repo_path, username)
209 210 return app(environ, start_response)
210 211 except Exception:
211 212 log.error(traceback.format_exc())
212 213 return HTTPInternalServerError()(environ, start_response)
213 214
214 215 def __make_app(self, repo_name, repo_path, username):
215 216 """
216 217 Make an wsgi application using dulserver
217 218
218 219 :param repo_name: name of the repository
219 220 :param repo_path: full path to the repository
220 221 """
221 222
222 223 from rhodecode.lib.middleware.pygrack import make_wsgi_app
223 224 app = make_wsgi_app(
224 225 repo_root=safe_str(self.basepath),
225 226 repo_name=repo_name,
226 227 username=username,
227 228 )
229 app = GunzipFilter(LimitedInputFilter(app))
228 230 return app
229 231
230 232 def __get_repository(self, environ):
231 233 """
232 234 Get's repository name out of PATH_INFO header
233 235
234 236 :param environ: environ where PATH_INFO is stored
235 237 """
236 238 try:
237 239 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
238 240 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
239 241 except:
240 242 log.error(traceback.format_exc())
241 243 raise
242 244
243 245 return repo_name
244 246
245 247 def __get_user(self, username):
246 248 return User.get_by_username(username)
247 249
248 250 def __get_action(self, environ):
249 251 """
250 252 Maps git request commands into a pull or push command.
251 253
252 254 :param environ:
253 255 """
254 256 service = environ['QUERY_STRING'].split('=')
255 257
256 258 if len(service) > 1:
257 259 service_cmd = service[1]
258 260 mapping = {
259 261 'git-receive-pack': 'push',
260 262 'git-upload-pack': 'pull',
261 263 }
262 264 op = mapping[service_cmd]
263 265 self._git_stored_op = op
264 266 return op
265 267 else:
266 268 # try to fallback to stored variable as we don't know if the last
267 269 # operation is pull/push
268 270 op = getattr(self, '_git_stored_op', 'pull')
269 271 return op
270 272
271 273 def _handle_githooks(self, repo_name, action, baseui, environ):
272 274 """
273 275 Handles pull action, push is handled by post-receive hook
274 276 """
275 277 from rhodecode.lib.hooks import log_pull_action
276 278 service = environ['QUERY_STRING'].split('=')
277 279 if len(service) < 2:
278 280 return
279 281
280 282 from rhodecode.model.db import Repository
281 283 _repo = Repository.get_by_repo_name(repo_name)
282 284 _repo = _repo.scm_instance
283 285 _repo._repo.ui = baseui
284 286
285 287 _hooks = dict(baseui.configitems('hooks')) or {}
286 288 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
287 289 log_pull_action(ui=baseui, repo=_repo._repo)
288 290
289 291 def __inject_extras(self, repo_path, baseui, extras={}):
290 292 """
291 293 Injects some extra params into baseui instance
292 294
293 295 :param baseui: baseui instance
294 296 :param extras: dict with extra params to put into baseui
295 297 """
296 298
297 299 # make our hgweb quiet so it doesn't print output
298 300 baseui.setconfig('ui', 'quiet', 'true')
299 301
300 302 #inject some additional parameters that will be available in ui
301 303 #for hooks
302 304 for k, v in extras.items():
303 305 baseui.setconfig('rhodecode_extras', k, v)
General Comments 0
You need to be logged in to leave comments. Login now