##// END OF EJS Templates
git: make sure we don't break streaming in case of empty pull messages....
marcink -
r277:90be8c49 stable
parent child Browse files
Show More
@@ -1,381 +1,386 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2017 RodeCode GmbH
2 # Copyright (C) 2014-2017 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 """Handles the Git smart protocol."""
18 """Handles the Git smart protocol."""
19
19
20 import os
20 import os
21 import socket
21 import socket
22 import logging
22 import logging
23
23
24 import simplejson as json
24 import simplejson as json
25 import dulwich.protocol
25 import dulwich.protocol
26 from webob import Request, Response, exc
26 from webob import Request, Response, exc
27
27
28 from vcsserver import hooks, subprocessio
28 from vcsserver import hooks, subprocessio
29
29
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class FileWrapper(object):
34 class FileWrapper(object):
35 """File wrapper that ensures how much data is read from it."""
35 """File wrapper that ensures how much data is read from it."""
36
36
37 def __init__(self, fd, content_length):
37 def __init__(self, fd, content_length):
38 self.fd = fd
38 self.fd = fd
39 self.content_length = content_length
39 self.content_length = content_length
40 self.remain = content_length
40 self.remain = content_length
41
41
42 def read(self, size):
42 def read(self, size):
43 if size <= self.remain:
43 if size <= self.remain:
44 try:
44 try:
45 data = self.fd.read(size)
45 data = self.fd.read(size)
46 except socket.error:
46 except socket.error:
47 raise IOError(self)
47 raise IOError(self)
48 self.remain -= size
48 self.remain -= size
49 elif self.remain:
49 elif self.remain:
50 data = self.fd.read(self.remain)
50 data = self.fd.read(self.remain)
51 self.remain = 0
51 self.remain = 0
52 else:
52 else:
53 data = None
53 data = None
54 return data
54 return data
55
55
56 def __repr__(self):
56 def __repr__(self):
57 return '<FileWrapper %s len: %s, read: %s>' % (
57 return '<FileWrapper %s len: %s, read: %s>' % (
58 self.fd, self.content_length, self.content_length - self.remain
58 self.fd, self.content_length, self.content_length - self.remain
59 )
59 )
60
60
61
61
62 class GitRepository(object):
62 class GitRepository(object):
63 """WSGI app for handling Git smart protocol endpoints."""
63 """WSGI app for handling Git smart protocol endpoints."""
64
64
65 git_folder_signature = frozenset(
65 git_folder_signature = frozenset(
66 ('config', 'head', 'info', 'objects', 'refs'))
66 ('config', 'head', 'info', 'objects', 'refs'))
67 commands = frozenset(('git-upload-pack', 'git-receive-pack'))
67 commands = frozenset(('git-upload-pack', 'git-receive-pack'))
68 valid_accepts = frozenset(('application/x-%s-result' %
68 valid_accepts = frozenset(('application/x-%s-result' %
69 c for c in commands))
69 c for c in commands))
70
70
71 # The last bytes are the SHA1 of the first 12 bytes.
71 # The last bytes are the SHA1 of the first 12 bytes.
72 EMPTY_PACK = (
72 EMPTY_PACK = (
73 'PACK\x00\x00\x00\x02\x00\x00\x00\x00' +
73 'PACK\x00\x00\x00\x02\x00\x00\x00\x00' +
74 '\x02\x9d\x08\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e'
74 '\x02\x9d\x08\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e'
75 )
75 )
76 SIDE_BAND_CAPS = frozenset(('side-band', 'side-band-64k'))
76 SIDE_BAND_CAPS = frozenset(('side-band', 'side-band-64k'))
77
77
78 def __init__(self, repo_name, content_path, git_path, update_server_info,
78 def __init__(self, repo_name, content_path, git_path, update_server_info,
79 extras):
79 extras):
80 files = frozenset(f.lower() for f in os.listdir(content_path))
80 files = frozenset(f.lower() for f in os.listdir(content_path))
81 valid_dir_signature = self.git_folder_signature.issubset(files)
81 valid_dir_signature = self.git_folder_signature.issubset(files)
82
82
83 if not valid_dir_signature:
83 if not valid_dir_signature:
84 raise OSError('%s missing git signature' % content_path)
84 raise OSError('%s missing git signature' % content_path)
85
85
86 self.content_path = content_path
86 self.content_path = content_path
87 self.repo_name = repo_name
87 self.repo_name = repo_name
88 self.extras = extras
88 self.extras = extras
89 self.git_path = git_path
89 self.git_path = git_path
90 self.update_server_info = update_server_info
90 self.update_server_info = update_server_info
91
91
92 def _get_fixedpath(self, path):
92 def _get_fixedpath(self, path):
93 """
93 """
94 Small fix for repo_path
94 Small fix for repo_path
95
95
96 :param path:
96 :param path:
97 """
97 """
98 path = path.split(self.repo_name, 1)[-1]
98 path = path.split(self.repo_name, 1)[-1]
99 if path.startswith('.git'):
99 if path.startswith('.git'):
100 # for bare repos we still get the .git prefix inside, we skip it
100 # for bare repos we still get the .git prefix inside, we skip it
101 # here, and remove from the service command
101 # here, and remove from the service command
102 path = path[4:]
102 path = path[4:]
103
103
104 return path.strip('/')
104 return path.strip('/')
105
105
106 def inforefs(self, request, unused_environ):
106 def inforefs(self, request, unused_environ):
107 """
107 """
108 WSGI Response producer for HTTP GET Git Smart
108 WSGI Response producer for HTTP GET Git Smart
109 HTTP /info/refs request.
109 HTTP /info/refs request.
110 """
110 """
111
111
112 git_command = request.GET.get('service')
112 git_command = request.GET.get('service')
113 if git_command not in self.commands:
113 if git_command not in self.commands:
114 log.debug('command %s not allowed', git_command)
114 log.debug('command %s not allowed', git_command)
115 return exc.HTTPForbidden()
115 return exc.HTTPForbidden()
116
116
117 # please, resist the urge to add '\n' to git capture and increment
117 # please, resist the urge to add '\n' to git capture and increment
118 # line count by 1.
118 # line count by 1.
119 # by git docs: Documentation/technical/http-protocol.txt#L214 \n is
119 # by git docs: Documentation/technical/http-protocol.txt#L214 \n is
120 # a part of protocol.
120 # a part of protocol.
121 # The code in Git client not only does NOT need '\n', but actually
121 # The code in Git client not only does NOT need '\n', but actually
122 # blows up if you sprinkle "flush" (0000) as "0001\n".
122 # blows up if you sprinkle "flush" (0000) as "0001\n".
123 # It reads binary, per number of bytes specified.
123 # It reads binary, per number of bytes specified.
124 # if you do add '\n' as part of data, count it.
124 # if you do add '\n' as part of data, count it.
125 server_advert = '# service=%s\n' % git_command
125 server_advert = '# service=%s\n' % git_command
126 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
126 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
127 try:
127 try:
128 gitenv = dict(os.environ)
128 gitenv = dict(os.environ)
129 # forget all configs
129 # forget all configs
130 gitenv['RC_SCM_DATA'] = json.dumps(self.extras)
130 gitenv['RC_SCM_DATA'] = json.dumps(self.extras)
131 command = [self.git_path, git_command[4:], '--stateless-rpc',
131 command = [self.git_path, git_command[4:], '--stateless-rpc',
132 '--advertise-refs', self.content_path]
132 '--advertise-refs', self.content_path]
133 out = subprocessio.SubprocessIOChunker(
133 out = subprocessio.SubprocessIOChunker(
134 command,
134 command,
135 env=gitenv,
135 env=gitenv,
136 starting_values=[packet_len + server_advert + '0000'],
136 starting_values=[packet_len + server_advert + '0000'],
137 shell=False
137 shell=False
138 )
138 )
139 except EnvironmentError:
139 except EnvironmentError:
140 log.exception('Error processing command')
140 log.exception('Error processing command')
141 raise exc.HTTPExpectationFailed()
141 raise exc.HTTPExpectationFailed()
142
142
143 resp = Response()
143 resp = Response()
144 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
144 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
145 resp.charset = None
145 resp.charset = None
146 resp.app_iter = out
146 resp.app_iter = out
147
147
148 return resp
148 return resp
149
149
150 def _get_want_capabilities(self, request):
150 def _get_want_capabilities(self, request):
151 """Read the capabilities found in the first want line of the request."""
151 """Read the capabilities found in the first want line of the request."""
152 pos = request.body_file_seekable.tell()
152 pos = request.body_file_seekable.tell()
153 first_line = request.body_file_seekable.readline()
153 first_line = request.body_file_seekable.readline()
154 request.body_file_seekable.seek(pos)
154 request.body_file_seekable.seek(pos)
155
155
156 return frozenset(
156 return frozenset(
157 dulwich.protocol.extract_want_line_capabilities(first_line)[1])
157 dulwich.protocol.extract_want_line_capabilities(first_line)[1])
158
158
159 def _build_failed_pre_pull_response(self, capabilities, pre_pull_messages):
159 def _build_failed_pre_pull_response(self, capabilities, pre_pull_messages):
160 """
160 """
161 Construct a response with an empty PACK file.
161 Construct a response with an empty PACK file.
162
162
163 We use an empty PACK file, as that would trigger the failure of the pull
163 We use an empty PACK file, as that would trigger the failure of the pull
164 or clone command.
164 or clone command.
165
165
166 We also print in the error output a message explaining why the command
166 We also print in the error output a message explaining why the command
167 was aborted.
167 was aborted.
168
168
169 If aditionally, the user is accepting messages we send them the output
169 If aditionally, the user is accepting messages we send them the output
170 of the pre-pull hook.
170 of the pre-pull hook.
171
171
172 Note that for clients not supporting side-band we just send them the
172 Note that for clients not supporting side-band we just send them the
173 emtpy PACK file.
173 emtpy PACK file.
174 """
174 """
175 if self.SIDE_BAND_CAPS.intersection(capabilities):
175 if self.SIDE_BAND_CAPS.intersection(capabilities):
176 response = []
176 response = []
177 proto = dulwich.protocol.Protocol(None, response.append)
177 proto = dulwich.protocol.Protocol(None, response.append)
178 proto.write_pkt_line('NAK\n')
178 proto.write_pkt_line('NAK\n')
179 self._write_sideband_to_proto(pre_pull_messages, proto,
179 self._write_sideband_to_proto(pre_pull_messages, proto,
180 capabilities)
180 capabilities)
181 # N.B.(skreft): Do not change the sideband channel to 3, as that
181 # N.B.(skreft): Do not change the sideband channel to 3, as that
182 # produces a fatal error in the client:
182 # produces a fatal error in the client:
183 # fatal: error in sideband demultiplexer
183 # fatal: error in sideband demultiplexer
184 proto.write_sideband(2, 'Pre pull hook failed: aborting\n')
184 proto.write_sideband(2, 'Pre pull hook failed: aborting\n')
185 proto.write_sideband(1, self.EMPTY_PACK)
185 proto.write_sideband(1, self.EMPTY_PACK)
186
186
187 # writes 0000
187 # writes 0000
188 proto.write_pkt_line(None)
188 proto.write_pkt_line(None)
189
189
190 return response
190 return response
191 else:
191 else:
192 return [self.EMPTY_PACK]
192 return [self.EMPTY_PACK]
193
193
194 def _write_sideband_to_proto(self, data, proto, capabilities):
194 def _write_sideband_to_proto(self, data, proto, capabilities):
195 """
195 """
196 Write the data to the proto's sideband number 2.
196 Write the data to the proto's sideband number 2.
197
197
198 We do not use dulwich's write_sideband directly as it only supports
198 We do not use dulwich's write_sideband directly as it only supports
199 side-band-64k.
199 side-band-64k.
200 """
200 """
201 if not data:
201 if not data:
202 return
202 return
203
203
204 # N.B.(skreft): The values below are explained in the pack protocol
204 # N.B.(skreft): The values below are explained in the pack protocol
205 # documentation, section Packfile Data.
205 # documentation, section Packfile Data.
206 # https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt
206 # https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt
207 if 'side-band-64k' in capabilities:
207 if 'side-band-64k' in capabilities:
208 chunk_size = 65515
208 chunk_size = 65515
209 elif 'side-band' in capabilities:
209 elif 'side-band' in capabilities:
210 chunk_size = 995
210 chunk_size = 995
211 else:
211 else:
212 return
212 return
213
213
214 chunker = (
214 chunker = (
215 data[i:i + chunk_size] for i in xrange(0, len(data), chunk_size))
215 data[i:i + chunk_size] for i in xrange(0, len(data), chunk_size))
216
216
217 for chunk in chunker:
217 for chunk in chunker:
218 proto.write_sideband(2, chunk)
218 proto.write_sideband(2, chunk)
219
219
220 def _get_messages(self, data, capabilities):
220 def _get_messages(self, data, capabilities):
221 """Return a list with packets for sending data in sideband number 2."""
221 """Return a list with packets for sending data in sideband number 2."""
222 response = []
222 response = []
223 proto = dulwich.protocol.Protocol(None, response.append)
223 proto = dulwich.protocol.Protocol(None, response.append)
224
224
225 self._write_sideband_to_proto(data, proto, capabilities)
225 self._write_sideband_to_proto(data, proto, capabilities)
226
226
227 return response
227 return response
228
228
229 def _inject_messages_to_response(self, response, capabilities,
229 def _inject_messages_to_response(self, response, capabilities,
230 start_messages, end_messages):
230 start_messages, end_messages):
231 """
231 """
232 Given a list reponse we inject the pre/post-pull messages.
232 Given a list response we inject the pre/post-pull messages.
233
233
234 We only inject the messages if the client supports sideband, and the
234 We only inject the messages if the client supports sideband, and the
235 response has the format:
235 response has the format:
236 0008NAK\n...0000
236 0008NAK\n...0000
237
237
238 Note that we do not check the no-progress capability as by default, git
238 Note that we do not check the no-progress capability as by default, git
239 sends it, which effectively would block all messages.
239 sends it, which effectively would block all messages.
240 """
240 """
241 if not self.SIDE_BAND_CAPS.intersection(capabilities):
241 if not self.SIDE_BAND_CAPS.intersection(capabilities):
242 return response
242 return response
243
243
244 if not start_messages and not end_messages:
245 return response
246
247 # make a list out of response if it's an iterator
248 # so we can investigate it for message injection.
249 if hasattr(response, '__iter__'):
250 response = list(response)
251
244 if (not response[0].startswith('0008NAK\n') or
252 if (not response[0].startswith('0008NAK\n') or
245 not response[-1].endswith('0000')):
253 not response[-1].endswith('0000')):
246 return response
254 return response
247
255
248 if not start_messages and not end_messages:
249 return response
250
251 new_response = ['0008NAK\n']
256 new_response = ['0008NAK\n']
252 new_response.extend(self._get_messages(start_messages, capabilities))
257 new_response.extend(self._get_messages(start_messages, capabilities))
253 if len(response) == 1:
258 if len(response) == 1:
254 new_response.append(response[0][8:-4])
259 new_response.append(response[0][8:-4])
255 else:
260 else:
256 new_response.append(response[0][8:])
261 new_response.append(response[0][8:])
257 new_response.extend(response[1:-1])
262 new_response.extend(response[1:-1])
258 new_response.append(response[-1][:-4])
263 new_response.append(response[-1][:-4])
259 new_response.extend(self._get_messages(end_messages, capabilities))
264 new_response.extend(self._get_messages(end_messages, capabilities))
260 new_response.append('0000')
265 new_response.append('0000')
261
266
262 return new_response
267 return new_response
263
268
264 def backend(self, request, environ):
269 def backend(self, request, environ):
265 """
270 """
266 WSGI Response producer for HTTP POST Git Smart HTTP requests.
271 WSGI Response producer for HTTP POST Git Smart HTTP requests.
267 Reads commands and data from HTTP POST's body.
272 Reads commands and data from HTTP POST's body.
268 returns an iterator obj with contents of git command's
273 returns an iterator obj with contents of git command's
269 response to stdout
274 response to stdout
270 """
275 """
271 # TODO(skreft): think how we could detect an HTTPLockedException, as
276 # TODO(skreft): think how we could detect an HTTPLockedException, as
272 # we probably want to have the same mechanism used by mercurial and
277 # we probably want to have the same mechanism used by mercurial and
273 # simplevcs.
278 # simplevcs.
274 # For that we would need to parse the output of the command looking for
279 # For that we would need to parse the output of the command looking for
275 # some signs of the HTTPLockedError, parse the data and reraise it in
280 # some signs of the HTTPLockedError, parse the data and reraise it in
276 # pygrack. However, that would interfere with the streaming.
281 # pygrack. However, that would interfere with the streaming.
277 #
282 #
278 # Now the output of a blocked push is:
283 # Now the output of a blocked push is:
279 # Pushing to http://test_regular:test12@127.0.0.1:5001/vcs_test_git
284 # Pushing to http://test_regular:test12@127.0.0.1:5001/vcs_test_git
280 # POST git-receive-pack (1047 bytes)
285 # POST git-receive-pack (1047 bytes)
281 # remote: ERROR: Repository `vcs_test_git` locked by user `test_admin`. Reason:`lock_auto`
286 # remote: ERROR: Repository `vcs_test_git` locked by user `test_admin`. Reason:`lock_auto`
282 # To http://test_regular:test12@127.0.0.1:5001/vcs_test_git
287 # To http://test_regular:test12@127.0.0.1:5001/vcs_test_git
283 # ! [remote rejected] master -> master (pre-receive hook declined)
288 # ! [remote rejected] master -> master (pre-receive hook declined)
284 # error: failed to push some refs to 'http://test_regular:test12@127.0.0.1:5001/vcs_test_git'
289 # error: failed to push some refs to 'http://test_regular:test12@127.0.0.1:5001/vcs_test_git'
285
290
286 git_command = self._get_fixedpath(request.path_info)
291 git_command = self._get_fixedpath(request.path_info)
287 if git_command not in self.commands:
292 if git_command not in self.commands:
288 log.debug('command %s not allowed', git_command)
293 log.debug('command %s not allowed', git_command)
289 return exc.HTTPForbidden()
294 return exc.HTTPForbidden()
290
295
291 capabilities = None
296 capabilities = None
292 if git_command == 'git-upload-pack':
297 if git_command == 'git-upload-pack':
293 capabilities = self._get_want_capabilities(request)
298 capabilities = self._get_want_capabilities(request)
294
299
295 if 'CONTENT_LENGTH' in environ:
300 if 'CONTENT_LENGTH' in environ:
296 inputstream = FileWrapper(request.body_file_seekable,
301 inputstream = FileWrapper(request.body_file_seekable,
297 request.content_length)
302 request.content_length)
298 else:
303 else:
299 inputstream = request.body_file_seekable
304 inputstream = request.body_file_seekable
300
305
301 resp = Response()
306 resp = Response()
302 resp.content_type = ('application/x-%s-result' %
307 resp.content_type = ('application/x-%s-result' %
303 git_command.encode('utf8'))
308 git_command.encode('utf8'))
304 resp.charset = None
309 resp.charset = None
305
310
311 pre_pull_messages = ''
306 if git_command == 'git-upload-pack':
312 if git_command == 'git-upload-pack':
307 status, pre_pull_messages = hooks.git_pre_pull(self.extras)
313 status, pre_pull_messages = hooks.git_pre_pull(self.extras)
308 if status != 0:
314 if status != 0:
309 resp.app_iter = self._build_failed_pre_pull_response(
315 resp.app_iter = self._build_failed_pre_pull_response(
310 capabilities, pre_pull_messages)
316 capabilities, pre_pull_messages)
311 return resp
317 return resp
312
318
313 gitenv = dict(os.environ)
319 gitenv = dict(os.environ)
314 # forget all configs
320 # forget all configs
315 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
321 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
316 gitenv['RC_SCM_DATA'] = json.dumps(self.extras)
322 gitenv['RC_SCM_DATA'] = json.dumps(self.extras)
317 cmd = [self.git_path, git_command[4:], '--stateless-rpc',
323 cmd = [self.git_path, git_command[4:], '--stateless-rpc',
318 self.content_path]
324 self.content_path]
319 log.debug('handling cmd %s', cmd)
325 log.debug('handling cmd %s', cmd)
320
326
321 out = subprocessio.SubprocessIOChunker(
327 out = subprocessio.SubprocessIOChunker(
322 cmd,
328 cmd,
323 inputstream=inputstream,
329 inputstream=inputstream,
324 env=gitenv,
330 env=gitenv,
325 cwd=self.content_path,
331 cwd=self.content_path,
326 shell=False,
332 shell=False,
327 fail_on_stderr=False,
333 fail_on_stderr=False,
328 fail_on_return_code=False
334 fail_on_return_code=False
329 )
335 )
330
336
331 if self.update_server_info and git_command == 'git-receive-pack':
337 if self.update_server_info and git_command == 'git-receive-pack':
332 # We need to fully consume the iterator here, as the
338 # We need to fully consume the iterator here, as the
333 # update-server-info command needs to be run after the push.
339 # update-server-info command needs to be run after the push.
334 out = list(out)
340 out = list(out)
335
341
336 # Updating refs manually after each push.
342 # Updating refs manually after each push.
337 # This is required as some clients are exposing Git repos internally
343 # This is required as some clients are exposing Git repos internally
338 # with the dumb protocol.
344 # with the dumb protocol.
339 cmd = [self.git_path, 'update-server-info']
345 cmd = [self.git_path, 'update-server-info']
340 log.debug('handling cmd %s', cmd)
346 log.debug('handling cmd %s', cmd)
341 output = subprocessio.SubprocessIOChunker(
347 output = subprocessio.SubprocessIOChunker(
342 cmd,
348 cmd,
343 inputstream=inputstream,
349 inputstream=inputstream,
344 env=gitenv,
350 env=gitenv,
345 cwd=self.content_path,
351 cwd=self.content_path,
346 shell=False,
352 shell=False,
347 fail_on_stderr=False,
353 fail_on_stderr=False,
348 fail_on_return_code=False
354 fail_on_return_code=False
349 )
355 )
350 # Consume all the output so the subprocess finishes
356 # Consume all the output so the subprocess finishes
351 for _ in output:
357 for _ in output:
352 pass
358 pass
353
359
354 if git_command == 'git-upload-pack':
360 if git_command == 'git-upload-pack':
355 out = list(out)
356 unused_status, post_pull_messages = hooks.git_post_pull(self.extras)
361 unused_status, post_pull_messages = hooks.git_post_pull(self.extras)
357 resp.app_iter = self._inject_messages_to_response(
362 resp.app_iter = self._inject_messages_to_response(
358 out, capabilities, pre_pull_messages, post_pull_messages)
363 out, capabilities, pre_pull_messages, post_pull_messages)
359 else:
364 else:
360 resp.app_iter = out
365 resp.app_iter = out
361
366
362 return resp
367 return resp
363
368
364 def __call__(self, environ, start_response):
369 def __call__(self, environ, start_response):
365 request = Request(environ)
370 request = Request(environ)
366 _path = self._get_fixedpath(request.path_info)
371 _path = self._get_fixedpath(request.path_info)
367 if _path.startswith('info/refs'):
372 if _path.startswith('info/refs'):
368 app = self.inforefs
373 app = self.inforefs
369 else:
374 else:
370 app = self.backend
375 app = self.backend
371
376
372 try:
377 try:
373 resp = app(request, environ)
378 resp = app(request, environ)
374 except exc.HTTPException as error:
379 except exc.HTTPException as error:
375 log.exception('HTTP Error')
380 log.exception('HTTP Error')
376 resp = error
381 resp = error
377 except Exception:
382 except Exception:
378 log.exception('Unknown error')
383 log.exception('Unknown error')
379 resp = exc.HTTPInternalServerError()
384 resp = exc.HTTPInternalServerError()
380
385
381 return resp(environ, start_response)
386 return resp(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now