##// END OF EJS Templates
bundle2: ignore errors seeking a bundle after an exception (issue4784)...
Gregory Szorc -
r32024:ad41739c default
parent child Browse files
Show More
@@ -1,1656 +1,1672 b''
1 # bundle2.py - generic container format to transmit arbitrary data.
1 # bundle2.py - generic container format to transmit arbitrary data.
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """Handling of the new bundle2 format
7 """Handling of the new bundle2 format
8
8
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 payloads in an application agnostic way. It consist in a sequence of "parts"
10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 that will be handed to and processed by the application layer.
11 that will be handed to and processed by the application layer.
12
12
13
13
14 General format architecture
14 General format architecture
15 ===========================
15 ===========================
16
16
17 The format is architectured as follow
17 The format is architectured as follow
18
18
19 - magic string
19 - magic string
20 - stream level parameters
20 - stream level parameters
21 - payload parts (any number)
21 - payload parts (any number)
22 - end of stream marker.
22 - end of stream marker.
23
23
24 the Binary format
24 the Binary format
25 ============================
25 ============================
26
26
27 All numbers are unsigned and big-endian.
27 All numbers are unsigned and big-endian.
28
28
29 stream level parameters
29 stream level parameters
30 ------------------------
30 ------------------------
31
31
32 Binary format is as follow
32 Binary format is as follow
33
33
34 :params size: int32
34 :params size: int32
35
35
36 The total number of Bytes used by the parameters
36 The total number of Bytes used by the parameters
37
37
38 :params value: arbitrary number of Bytes
38 :params value: arbitrary number of Bytes
39
39
40 A blob of `params size` containing the serialized version of all stream level
40 A blob of `params size` containing the serialized version of all stream level
41 parameters.
41 parameters.
42
42
43 The blob contains a space separated list of parameters. Parameters with value
43 The blob contains a space separated list of parameters. Parameters with value
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45
45
46 Empty name are obviously forbidden.
46 Empty name are obviously forbidden.
47
47
48 Name MUST start with a letter. If this first letter is lower case, the
48 Name MUST start with a letter. If this first letter is lower case, the
49 parameter is advisory and can be safely ignored. However when the first
49 parameter is advisory and can be safely ignored. However when the first
50 letter is capital, the parameter is mandatory and the bundling process MUST
50 letter is capital, the parameter is mandatory and the bundling process MUST
51 stop if he is not able to proceed it.
51 stop if he is not able to proceed it.
52
52
53 Stream parameters use a simple textual format for two main reasons:
53 Stream parameters use a simple textual format for two main reasons:
54
54
55 - Stream level parameters should remain simple and we want to discourage any
55 - Stream level parameters should remain simple and we want to discourage any
56 crazy usage.
56 crazy usage.
57 - Textual data allow easy human inspection of a bundle2 header in case of
57 - Textual data allow easy human inspection of a bundle2 header in case of
58 troubles.
58 troubles.
59
59
60 Any Applicative level options MUST go into a bundle2 part instead.
60 Any Applicative level options MUST go into a bundle2 part instead.
61
61
62 Payload part
62 Payload part
63 ------------------------
63 ------------------------
64
64
65 Binary format is as follow
65 Binary format is as follow
66
66
67 :header size: int32
67 :header size: int32
68
68
69 The total number of Bytes used by the part header. When the header is empty
69 The total number of Bytes used by the part header. When the header is empty
70 (size = 0) this is interpreted as the end of stream marker.
70 (size = 0) this is interpreted as the end of stream marker.
71
71
72 :header:
72 :header:
73
73
74 The header defines how to interpret the part. It contains two piece of
74 The header defines how to interpret the part. It contains two piece of
75 data: the part type, and the part parameters.
75 data: the part type, and the part parameters.
76
76
77 The part type is used to route an application level handler, that can
77 The part type is used to route an application level handler, that can
78 interpret payload.
78 interpret payload.
79
79
80 Part parameters are passed to the application level handler. They are
80 Part parameters are passed to the application level handler. They are
81 meant to convey information that will help the application level object to
81 meant to convey information that will help the application level object to
82 interpret the part payload.
82 interpret the part payload.
83
83
84 The binary format of the header is has follow
84 The binary format of the header is has follow
85
85
86 :typesize: (one byte)
86 :typesize: (one byte)
87
87
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89
89
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 to this part.
91 to this part.
92
92
93 :parameters:
93 :parameters:
94
94
95 Part's parameter may have arbitrary content, the binary structure is::
95 Part's parameter may have arbitrary content, the binary structure is::
96
96
97 <mandatory-count><advisory-count><param-sizes><param-data>
97 <mandatory-count><advisory-count><param-sizes><param-data>
98
98
99 :mandatory-count: 1 byte, number of mandatory parameters
99 :mandatory-count: 1 byte, number of mandatory parameters
100
100
101 :advisory-count: 1 byte, number of advisory parameters
101 :advisory-count: 1 byte, number of advisory parameters
102
102
103 :param-sizes:
103 :param-sizes:
104
104
105 N couple of bytes, where N is the total number of parameters. Each
105 N couple of bytes, where N is the total number of parameters. Each
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107
107
108 :param-data:
108 :param-data:
109
109
110 A blob of bytes from which each parameter key and value can be
110 A blob of bytes from which each parameter key and value can be
111 retrieved using the list of size couples stored in the previous
111 retrieved using the list of size couples stored in the previous
112 field.
112 field.
113
113
114 Mandatory parameters comes first, then the advisory ones.
114 Mandatory parameters comes first, then the advisory ones.
115
115
116 Each parameter's key MUST be unique within the part.
116 Each parameter's key MUST be unique within the part.
117
117
118 :payload:
118 :payload:
119
119
120 payload is a series of `<chunksize><chunkdata>`.
120 payload is a series of `<chunksize><chunkdata>`.
121
121
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124
124
125 The current implementation always produces either zero or one chunk.
125 The current implementation always produces either zero or one chunk.
126 This is an implementation limitation that will ultimately be lifted.
126 This is an implementation limitation that will ultimately be lifted.
127
127
128 `chunksize` can be negative to trigger special case processing. No such
128 `chunksize` can be negative to trigger special case processing. No such
129 processing is in place yet.
129 processing is in place yet.
130
130
131 Bundle processing
131 Bundle processing
132 ============================
132 ============================
133
133
134 Each part is processed in order using a "part handler". Handler are registered
134 Each part is processed in order using a "part handler". Handler are registered
135 for a certain part type.
135 for a certain part type.
136
136
137 The matching of a part to its handler is case insensitive. The case of the
137 The matching of a part to its handler is case insensitive. The case of the
138 part type is used to know if a part is mandatory or advisory. If the Part type
138 part type is used to know if a part is mandatory or advisory. If the Part type
139 contains any uppercase char it is considered mandatory. When no handler is
139 contains any uppercase char it is considered mandatory. When no handler is
140 known for a Mandatory part, the process is aborted and an exception is raised.
140 known for a Mandatory part, the process is aborted and an exception is raised.
141 If the part is advisory and no handler is known, the part is ignored. When the
141 If the part is advisory and no handler is known, the part is ignored. When the
142 process is aborted, the full bundle is still read from the stream to keep the
142 process is aborted, the full bundle is still read from the stream to keep the
143 channel usable. But none of the part read from an abort are processed. In the
143 channel usable. But none of the part read from an abort are processed. In the
144 future, dropping the stream may become an option for channel we do not care to
144 future, dropping the stream may become an option for channel we do not care to
145 preserve.
145 preserve.
146 """
146 """
147
147
148 from __future__ import absolute_import
148 from __future__ import absolute_import
149
149
150 import errno
150 import errno
151 import re
151 import re
152 import string
152 import string
153 import struct
153 import struct
154 import sys
154 import sys
155
155
156 from .i18n import _
156 from .i18n import _
157 from . import (
157 from . import (
158 changegroup,
158 changegroup,
159 error,
159 error,
160 obsolete,
160 obsolete,
161 pushkey,
161 pushkey,
162 pycompat,
162 pycompat,
163 tags,
163 tags,
164 url,
164 url,
165 util,
165 util,
166 )
166 )
167
167
168 urlerr = util.urlerr
168 urlerr = util.urlerr
169 urlreq = util.urlreq
169 urlreq = util.urlreq
170
170
171 _pack = struct.pack
171 _pack = struct.pack
172 _unpack = struct.unpack
172 _unpack = struct.unpack
173
173
174 _fstreamparamsize = '>i'
174 _fstreamparamsize = '>i'
175 _fpartheadersize = '>i'
175 _fpartheadersize = '>i'
176 _fparttypesize = '>B'
176 _fparttypesize = '>B'
177 _fpartid = '>I'
177 _fpartid = '>I'
178 _fpayloadsize = '>i'
178 _fpayloadsize = '>i'
179 _fpartparamcount = '>BB'
179 _fpartparamcount = '>BB'
180
180
181 preferedchunksize = 4096
181 preferedchunksize = 4096
182
182
183 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
183 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
184
184
185 def outdebug(ui, message):
185 def outdebug(ui, message):
186 """debug regarding output stream (bundling)"""
186 """debug regarding output stream (bundling)"""
187 if ui.configbool('devel', 'bundle2.debug', False):
187 if ui.configbool('devel', 'bundle2.debug', False):
188 ui.debug('bundle2-output: %s\n' % message)
188 ui.debug('bundle2-output: %s\n' % message)
189
189
190 def indebug(ui, message):
190 def indebug(ui, message):
191 """debug on input stream (unbundling)"""
191 """debug on input stream (unbundling)"""
192 if ui.configbool('devel', 'bundle2.debug', False):
192 if ui.configbool('devel', 'bundle2.debug', False):
193 ui.debug('bundle2-input: %s\n' % message)
193 ui.debug('bundle2-input: %s\n' % message)
194
194
195 def validateparttype(parttype):
195 def validateparttype(parttype):
196 """raise ValueError if a parttype contains invalid character"""
196 """raise ValueError if a parttype contains invalid character"""
197 if _parttypeforbidden.search(parttype):
197 if _parttypeforbidden.search(parttype):
198 raise ValueError(parttype)
198 raise ValueError(parttype)
199
199
200 def _makefpartparamsizes(nbparams):
200 def _makefpartparamsizes(nbparams):
201 """return a struct format to read part parameter sizes
201 """return a struct format to read part parameter sizes
202
202
203 The number parameters is variable so we need to build that format
203 The number parameters is variable so we need to build that format
204 dynamically.
204 dynamically.
205 """
205 """
206 return '>'+('BB'*nbparams)
206 return '>'+('BB'*nbparams)
207
207
208 parthandlermapping = {}
208 parthandlermapping = {}
209
209
210 def parthandler(parttype, params=()):
210 def parthandler(parttype, params=()):
211 """decorator that register a function as a bundle2 part handler
211 """decorator that register a function as a bundle2 part handler
212
212
213 eg::
213 eg::
214
214
215 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
215 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
216 def myparttypehandler(...):
216 def myparttypehandler(...):
217 '''process a part of type "my part".'''
217 '''process a part of type "my part".'''
218 ...
218 ...
219 """
219 """
220 validateparttype(parttype)
220 validateparttype(parttype)
221 def _decorator(func):
221 def _decorator(func):
222 lparttype = parttype.lower() # enforce lower case matching.
222 lparttype = parttype.lower() # enforce lower case matching.
223 assert lparttype not in parthandlermapping
223 assert lparttype not in parthandlermapping
224 parthandlermapping[lparttype] = func
224 parthandlermapping[lparttype] = func
225 func.params = frozenset(params)
225 func.params = frozenset(params)
226 return func
226 return func
227 return _decorator
227 return _decorator
228
228
229 class unbundlerecords(object):
229 class unbundlerecords(object):
230 """keep record of what happens during and unbundle
230 """keep record of what happens during and unbundle
231
231
232 New records are added using `records.add('cat', obj)`. Where 'cat' is a
232 New records are added using `records.add('cat', obj)`. Where 'cat' is a
233 category of record and obj is an arbitrary object.
233 category of record and obj is an arbitrary object.
234
234
235 `records['cat']` will return all entries of this category 'cat'.
235 `records['cat']` will return all entries of this category 'cat'.
236
236
237 Iterating on the object itself will yield `('category', obj)` tuples
237 Iterating on the object itself will yield `('category', obj)` tuples
238 for all entries.
238 for all entries.
239
239
240 All iterations happens in chronological order.
240 All iterations happens in chronological order.
241 """
241 """
242
242
243 def __init__(self):
243 def __init__(self):
244 self._categories = {}
244 self._categories = {}
245 self._sequences = []
245 self._sequences = []
246 self._replies = {}
246 self._replies = {}
247
247
248 def add(self, category, entry, inreplyto=None):
248 def add(self, category, entry, inreplyto=None):
249 """add a new record of a given category.
249 """add a new record of a given category.
250
250
251 The entry can then be retrieved in the list returned by
251 The entry can then be retrieved in the list returned by
252 self['category']."""
252 self['category']."""
253 self._categories.setdefault(category, []).append(entry)
253 self._categories.setdefault(category, []).append(entry)
254 self._sequences.append((category, entry))
254 self._sequences.append((category, entry))
255 if inreplyto is not None:
255 if inreplyto is not None:
256 self.getreplies(inreplyto).add(category, entry)
256 self.getreplies(inreplyto).add(category, entry)
257
257
258 def getreplies(self, partid):
258 def getreplies(self, partid):
259 """get the records that are replies to a specific part"""
259 """get the records that are replies to a specific part"""
260 return self._replies.setdefault(partid, unbundlerecords())
260 return self._replies.setdefault(partid, unbundlerecords())
261
261
262 def __getitem__(self, cat):
262 def __getitem__(self, cat):
263 return tuple(self._categories.get(cat, ()))
263 return tuple(self._categories.get(cat, ()))
264
264
265 def __iter__(self):
265 def __iter__(self):
266 return iter(self._sequences)
266 return iter(self._sequences)
267
267
268 def __len__(self):
268 def __len__(self):
269 return len(self._sequences)
269 return len(self._sequences)
270
270
271 def __nonzero__(self):
271 def __nonzero__(self):
272 return bool(self._sequences)
272 return bool(self._sequences)
273
273
274 __bool__ = __nonzero__
274 __bool__ = __nonzero__
275
275
276 class bundleoperation(object):
276 class bundleoperation(object):
277 """an object that represents a single bundling process
277 """an object that represents a single bundling process
278
278
279 Its purpose is to carry unbundle-related objects and states.
279 Its purpose is to carry unbundle-related objects and states.
280
280
281 A new object should be created at the beginning of each bundle processing.
281 A new object should be created at the beginning of each bundle processing.
282 The object is to be returned by the processing function.
282 The object is to be returned by the processing function.
283
283
284 The object has very little content now it will ultimately contain:
284 The object has very little content now it will ultimately contain:
285 * an access to the repo the bundle is applied to,
285 * an access to the repo the bundle is applied to,
286 * a ui object,
286 * a ui object,
287 * a way to retrieve a transaction to add changes to the repo,
287 * a way to retrieve a transaction to add changes to the repo,
288 * a way to record the result of processing each part,
288 * a way to record the result of processing each part,
289 * a way to construct a bundle response when applicable.
289 * a way to construct a bundle response when applicable.
290 """
290 """
291
291
292 def __init__(self, repo, transactiongetter, captureoutput=True):
292 def __init__(self, repo, transactiongetter, captureoutput=True):
293 self.repo = repo
293 self.repo = repo
294 self.ui = repo.ui
294 self.ui = repo.ui
295 self.records = unbundlerecords()
295 self.records = unbundlerecords()
296 self.gettransaction = transactiongetter
296 self.gettransaction = transactiongetter
297 self.reply = None
297 self.reply = None
298 self.captureoutput = captureoutput
298 self.captureoutput = captureoutput
299
299
300 class TransactionUnavailable(RuntimeError):
300 class TransactionUnavailable(RuntimeError):
301 pass
301 pass
302
302
303 def _notransaction():
303 def _notransaction():
304 """default method to get a transaction while processing a bundle
304 """default method to get a transaction while processing a bundle
305
305
306 Raise an exception to highlight the fact that no transaction was expected
306 Raise an exception to highlight the fact that no transaction was expected
307 to be created"""
307 to be created"""
308 raise TransactionUnavailable()
308 raise TransactionUnavailable()
309
309
310 def applybundle(repo, unbundler, tr, source=None, url=None, op=None):
310 def applybundle(repo, unbundler, tr, source=None, url=None, op=None):
311 # transform me into unbundler.apply() as soon as the freeze is lifted
311 # transform me into unbundler.apply() as soon as the freeze is lifted
312 tr.hookargs['bundle2'] = '1'
312 tr.hookargs['bundle2'] = '1'
313 if source is not None and 'source' not in tr.hookargs:
313 if source is not None and 'source' not in tr.hookargs:
314 tr.hookargs['source'] = source
314 tr.hookargs['source'] = source
315 if url is not None and 'url' not in tr.hookargs:
315 if url is not None and 'url' not in tr.hookargs:
316 tr.hookargs['url'] = url
316 tr.hookargs['url'] = url
317 return processbundle(repo, unbundler, lambda: tr, op=op)
317 return processbundle(repo, unbundler, lambda: tr, op=op)
318
318
319 def processbundle(repo, unbundler, transactiongetter=None, op=None):
319 def processbundle(repo, unbundler, transactiongetter=None, op=None):
320 """This function process a bundle, apply effect to/from a repo
320 """This function process a bundle, apply effect to/from a repo
321
321
322 It iterates over each part then searches for and uses the proper handling
322 It iterates over each part then searches for and uses the proper handling
323 code to process the part. Parts are processed in order.
323 code to process the part. Parts are processed in order.
324
324
325 Unknown Mandatory part will abort the process.
325 Unknown Mandatory part will abort the process.
326
326
327 It is temporarily possible to provide a prebuilt bundleoperation to the
327 It is temporarily possible to provide a prebuilt bundleoperation to the
328 function. This is used to ensure output is properly propagated in case of
328 function. This is used to ensure output is properly propagated in case of
329 an error during the unbundling. This output capturing part will likely be
329 an error during the unbundling. This output capturing part will likely be
330 reworked and this ability will probably go away in the process.
330 reworked and this ability will probably go away in the process.
331 """
331 """
332 if op is None:
332 if op is None:
333 if transactiongetter is None:
333 if transactiongetter is None:
334 transactiongetter = _notransaction
334 transactiongetter = _notransaction
335 op = bundleoperation(repo, transactiongetter)
335 op = bundleoperation(repo, transactiongetter)
336 # todo:
336 # todo:
337 # - replace this is a init function soon.
337 # - replace this is a init function soon.
338 # - exception catching
338 # - exception catching
339 unbundler.params
339 unbundler.params
340 if repo.ui.debugflag:
340 if repo.ui.debugflag:
341 msg = ['bundle2-input-bundle:']
341 msg = ['bundle2-input-bundle:']
342 if unbundler.params:
342 if unbundler.params:
343 msg.append(' %i params')
343 msg.append(' %i params')
344 if op.gettransaction is None:
344 if op.gettransaction is None:
345 msg.append(' no-transaction')
345 msg.append(' no-transaction')
346 else:
346 else:
347 msg.append(' with-transaction')
347 msg.append(' with-transaction')
348 msg.append('\n')
348 msg.append('\n')
349 repo.ui.debug(''.join(msg))
349 repo.ui.debug(''.join(msg))
350 iterparts = enumerate(unbundler.iterparts())
350 iterparts = enumerate(unbundler.iterparts())
351 part = None
351 part = None
352 nbpart = 0
352 nbpart = 0
353 try:
353 try:
354 for nbpart, part in iterparts:
354 for nbpart, part in iterparts:
355 _processpart(op, part)
355 _processpart(op, part)
356 except Exception as exc:
356 except Exception as exc:
357 for nbpart, part in iterparts:
357 # Any exceptions seeking to the end of the bundle at this point are
358 # consume the bundle content
358 # almost certainly related to the underlying stream being bad.
359 part.seek(0, 2)
359 # And, chances are that the exception we're handling is related to
360 # getting in that bad state. So, we swallow the seeking error and
361 # re-raise the original error.
362 seekerror = False
363 try:
364 for nbpart, part in iterparts:
365 # consume the bundle content
366 part.seek(0, 2)
367 except Exception:
368 seekerror = True
369
360 # Small hack to let caller code distinguish exceptions from bundle2
370 # Small hack to let caller code distinguish exceptions from bundle2
361 # processing from processing the old format. This is mostly
371 # processing from processing the old format. This is mostly
362 # needed to handle different return codes to unbundle according to the
372 # needed to handle different return codes to unbundle according to the
363 # type of bundle. We should probably clean up or drop this return code
373 # type of bundle. We should probably clean up or drop this return code
364 # craziness in a future version.
374 # craziness in a future version.
365 exc.duringunbundle2 = True
375 exc.duringunbundle2 = True
366 salvaged = []
376 salvaged = []
367 replycaps = None
377 replycaps = None
368 if op.reply is not None:
378 if op.reply is not None:
369 salvaged = op.reply.salvageoutput()
379 salvaged = op.reply.salvageoutput()
370 replycaps = op.reply.capabilities
380 replycaps = op.reply.capabilities
371 exc._replycaps = replycaps
381 exc._replycaps = replycaps
372 exc._bundle2salvagedoutput = salvaged
382 exc._bundle2salvagedoutput = salvaged
373 raise
383
384 # Re-raising from a variable loses the original stack. So only use
385 # that form if we need to.
386 if seekerror:
387 raise exc
388 else:
389 raise
374 finally:
390 finally:
375 repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
391 repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
376
392
377 return op
393 return op
378
394
379 def _processpart(op, part):
395 def _processpart(op, part):
380 """process a single part from a bundle
396 """process a single part from a bundle
381
397
382 The part is guaranteed to have been fully consumed when the function exits
398 The part is guaranteed to have been fully consumed when the function exits
383 (even if an exception is raised)."""
399 (even if an exception is raised)."""
384 status = 'unknown' # used by debug output
400 status = 'unknown' # used by debug output
385 hardabort = False
401 hardabort = False
386 try:
402 try:
387 try:
403 try:
388 handler = parthandlermapping.get(part.type)
404 handler = parthandlermapping.get(part.type)
389 if handler is None:
405 if handler is None:
390 status = 'unsupported-type'
406 status = 'unsupported-type'
391 raise error.BundleUnknownFeatureError(parttype=part.type)
407 raise error.BundleUnknownFeatureError(parttype=part.type)
392 indebug(op.ui, 'found a handler for part %r' % part.type)
408 indebug(op.ui, 'found a handler for part %r' % part.type)
393 unknownparams = part.mandatorykeys - handler.params
409 unknownparams = part.mandatorykeys - handler.params
394 if unknownparams:
410 if unknownparams:
395 unknownparams = list(unknownparams)
411 unknownparams = list(unknownparams)
396 unknownparams.sort()
412 unknownparams.sort()
397 status = 'unsupported-params (%s)' % unknownparams
413 status = 'unsupported-params (%s)' % unknownparams
398 raise error.BundleUnknownFeatureError(parttype=part.type,
414 raise error.BundleUnknownFeatureError(parttype=part.type,
399 params=unknownparams)
415 params=unknownparams)
400 status = 'supported'
416 status = 'supported'
401 except error.BundleUnknownFeatureError as exc:
417 except error.BundleUnknownFeatureError as exc:
402 if part.mandatory: # mandatory parts
418 if part.mandatory: # mandatory parts
403 raise
419 raise
404 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
420 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
405 return # skip to part processing
421 return # skip to part processing
406 finally:
422 finally:
407 if op.ui.debugflag:
423 if op.ui.debugflag:
408 msg = ['bundle2-input-part: "%s"' % part.type]
424 msg = ['bundle2-input-part: "%s"' % part.type]
409 if not part.mandatory:
425 if not part.mandatory:
410 msg.append(' (advisory)')
426 msg.append(' (advisory)')
411 nbmp = len(part.mandatorykeys)
427 nbmp = len(part.mandatorykeys)
412 nbap = len(part.params) - nbmp
428 nbap = len(part.params) - nbmp
413 if nbmp or nbap:
429 if nbmp or nbap:
414 msg.append(' (params:')
430 msg.append(' (params:')
415 if nbmp:
431 if nbmp:
416 msg.append(' %i mandatory' % nbmp)
432 msg.append(' %i mandatory' % nbmp)
417 if nbap:
433 if nbap:
418 msg.append(' %i advisory' % nbmp)
434 msg.append(' %i advisory' % nbmp)
419 msg.append(')')
435 msg.append(')')
420 msg.append(' %s\n' % status)
436 msg.append(' %s\n' % status)
421 op.ui.debug(''.join(msg))
437 op.ui.debug(''.join(msg))
422
438
423 # handler is called outside the above try block so that we don't
439 # handler is called outside the above try block so that we don't
424 # risk catching KeyErrors from anything other than the
440 # risk catching KeyErrors from anything other than the
425 # parthandlermapping lookup (any KeyError raised by handler()
441 # parthandlermapping lookup (any KeyError raised by handler()
426 # itself represents a defect of a different variety).
442 # itself represents a defect of a different variety).
427 output = None
443 output = None
428 if op.captureoutput and op.reply is not None:
444 if op.captureoutput and op.reply is not None:
429 op.ui.pushbuffer(error=True, subproc=True)
445 op.ui.pushbuffer(error=True, subproc=True)
430 output = ''
446 output = ''
431 try:
447 try:
432 handler(op, part)
448 handler(op, part)
433 finally:
449 finally:
434 if output is not None:
450 if output is not None:
435 output = op.ui.popbuffer()
451 output = op.ui.popbuffer()
436 if output:
452 if output:
437 outpart = op.reply.newpart('output', data=output,
453 outpart = op.reply.newpart('output', data=output,
438 mandatory=False)
454 mandatory=False)
439 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
455 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
440 # If exiting or interrupted, do not attempt to seek the stream in the
456 # If exiting or interrupted, do not attempt to seek the stream in the
441 # finally block below. This makes abort faster.
457 # finally block below. This makes abort faster.
442 except (SystemExit, KeyboardInterrupt):
458 except (SystemExit, KeyboardInterrupt):
443 hardabort = True
459 hardabort = True
444 raise
460 raise
445 finally:
461 finally:
446 # consume the part content to not corrupt the stream.
462 # consume the part content to not corrupt the stream.
447 if not hardabort:
463 if not hardabort:
448 part.seek(0, 2)
464 part.seek(0, 2)
449
465
450
466
451 def decodecaps(blob):
467 def decodecaps(blob):
452 """decode a bundle2 caps bytes blob into a dictionary
468 """decode a bundle2 caps bytes blob into a dictionary
453
469
454 The blob is a list of capabilities (one per line)
470 The blob is a list of capabilities (one per line)
455 Capabilities may have values using a line of the form::
471 Capabilities may have values using a line of the form::
456
472
457 capability=value1,value2,value3
473 capability=value1,value2,value3
458
474
459 The values are always a list."""
475 The values are always a list."""
460 caps = {}
476 caps = {}
461 for line in blob.splitlines():
477 for line in blob.splitlines():
462 if not line:
478 if not line:
463 continue
479 continue
464 if '=' not in line:
480 if '=' not in line:
465 key, vals = line, ()
481 key, vals = line, ()
466 else:
482 else:
467 key, vals = line.split('=', 1)
483 key, vals = line.split('=', 1)
468 vals = vals.split(',')
484 vals = vals.split(',')
469 key = urlreq.unquote(key)
485 key = urlreq.unquote(key)
470 vals = [urlreq.unquote(v) for v in vals]
486 vals = [urlreq.unquote(v) for v in vals]
471 caps[key] = vals
487 caps[key] = vals
472 return caps
488 return caps
473
489
474 def encodecaps(caps):
490 def encodecaps(caps):
475 """encode a bundle2 caps dictionary into a bytes blob"""
491 """encode a bundle2 caps dictionary into a bytes blob"""
476 chunks = []
492 chunks = []
477 for ca in sorted(caps):
493 for ca in sorted(caps):
478 vals = caps[ca]
494 vals = caps[ca]
479 ca = urlreq.quote(ca)
495 ca = urlreq.quote(ca)
480 vals = [urlreq.quote(v) for v in vals]
496 vals = [urlreq.quote(v) for v in vals]
481 if vals:
497 if vals:
482 ca = "%s=%s" % (ca, ','.join(vals))
498 ca = "%s=%s" % (ca, ','.join(vals))
483 chunks.append(ca)
499 chunks.append(ca)
484 return '\n'.join(chunks)
500 return '\n'.join(chunks)
485
501
486 bundletypes = {
502 bundletypes = {
487 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
503 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
488 # since the unification ssh accepts a header but there
504 # since the unification ssh accepts a header but there
489 # is no capability signaling it.
505 # is no capability signaling it.
490 "HG20": (), # special-cased below
506 "HG20": (), # special-cased below
491 "HG10UN": ("HG10UN", 'UN'),
507 "HG10UN": ("HG10UN", 'UN'),
492 "HG10BZ": ("HG10", 'BZ'),
508 "HG10BZ": ("HG10", 'BZ'),
493 "HG10GZ": ("HG10GZ", 'GZ'),
509 "HG10GZ": ("HG10GZ", 'GZ'),
494 }
510 }
495
511
496 # hgweb uses this list to communicate its preferred type
512 # hgweb uses this list to communicate its preferred type
497 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
513 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
498
514
499 class bundle20(object):
515 class bundle20(object):
500 """represent an outgoing bundle2 container
516 """represent an outgoing bundle2 container
501
517
502 Use the `addparam` method to add stream level parameter. and `newpart` to
518 Use the `addparam` method to add stream level parameter. and `newpart` to
503 populate it. Then call `getchunks` to retrieve all the binary chunks of
519 populate it. Then call `getchunks` to retrieve all the binary chunks of
504 data that compose the bundle2 container."""
520 data that compose the bundle2 container."""
505
521
506 _magicstring = 'HG20'
522 _magicstring = 'HG20'
507
523
508 def __init__(self, ui, capabilities=()):
524 def __init__(self, ui, capabilities=()):
509 self.ui = ui
525 self.ui = ui
510 self._params = []
526 self._params = []
511 self._parts = []
527 self._parts = []
512 self.capabilities = dict(capabilities)
528 self.capabilities = dict(capabilities)
513 self._compengine = util.compengines.forbundletype('UN')
529 self._compengine = util.compengines.forbundletype('UN')
514 self._compopts = None
530 self._compopts = None
515
531
516 def setcompression(self, alg, compopts=None):
532 def setcompression(self, alg, compopts=None):
517 """setup core part compression to <alg>"""
533 """setup core part compression to <alg>"""
518 if alg in (None, 'UN'):
534 if alg in (None, 'UN'):
519 return
535 return
520 assert not any(n.lower() == 'compression' for n, v in self._params)
536 assert not any(n.lower() == 'compression' for n, v in self._params)
521 self.addparam('Compression', alg)
537 self.addparam('Compression', alg)
522 self._compengine = util.compengines.forbundletype(alg)
538 self._compengine = util.compengines.forbundletype(alg)
523 self._compopts = compopts
539 self._compopts = compopts
524
540
525 @property
541 @property
526 def nbparts(self):
542 def nbparts(self):
527 """total number of parts added to the bundler"""
543 """total number of parts added to the bundler"""
528 return len(self._parts)
544 return len(self._parts)
529
545
530 # methods used to defines the bundle2 content
546 # methods used to defines the bundle2 content
531 def addparam(self, name, value=None):
547 def addparam(self, name, value=None):
532 """add a stream level parameter"""
548 """add a stream level parameter"""
533 if not name:
549 if not name:
534 raise ValueError('empty parameter name')
550 raise ValueError('empty parameter name')
535 if name[0] not in string.letters:
551 if name[0] not in string.letters:
536 raise ValueError('non letter first character: %r' % name)
552 raise ValueError('non letter first character: %r' % name)
537 self._params.append((name, value))
553 self._params.append((name, value))
538
554
539 def addpart(self, part):
555 def addpart(self, part):
540 """add a new part to the bundle2 container
556 """add a new part to the bundle2 container
541
557
542 Parts contains the actual applicative payload."""
558 Parts contains the actual applicative payload."""
543 assert part.id is None
559 assert part.id is None
544 part.id = len(self._parts) # very cheap counter
560 part.id = len(self._parts) # very cheap counter
545 self._parts.append(part)
561 self._parts.append(part)
546
562
547 def newpart(self, typeid, *args, **kwargs):
563 def newpart(self, typeid, *args, **kwargs):
548 """create a new part and add it to the containers
564 """create a new part and add it to the containers
549
565
550 As the part is directly added to the containers. For now, this means
566 As the part is directly added to the containers. For now, this means
551 that any failure to properly initialize the part after calling
567 that any failure to properly initialize the part after calling
552 ``newpart`` should result in a failure of the whole bundling process.
568 ``newpart`` should result in a failure of the whole bundling process.
553
569
554 You can still fall back to manually create and add if you need better
570 You can still fall back to manually create and add if you need better
555 control."""
571 control."""
556 part = bundlepart(typeid, *args, **kwargs)
572 part = bundlepart(typeid, *args, **kwargs)
557 self.addpart(part)
573 self.addpart(part)
558 return part
574 return part
559
575
560 # methods used to generate the bundle2 stream
576 # methods used to generate the bundle2 stream
561 def getchunks(self):
577 def getchunks(self):
562 if self.ui.debugflag:
578 if self.ui.debugflag:
563 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
579 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
564 if self._params:
580 if self._params:
565 msg.append(' (%i params)' % len(self._params))
581 msg.append(' (%i params)' % len(self._params))
566 msg.append(' %i parts total\n' % len(self._parts))
582 msg.append(' %i parts total\n' % len(self._parts))
567 self.ui.debug(''.join(msg))
583 self.ui.debug(''.join(msg))
568 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
584 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
569 yield self._magicstring
585 yield self._magicstring
570 param = self._paramchunk()
586 param = self._paramchunk()
571 outdebug(self.ui, 'bundle parameter: %s' % param)
587 outdebug(self.ui, 'bundle parameter: %s' % param)
572 yield _pack(_fstreamparamsize, len(param))
588 yield _pack(_fstreamparamsize, len(param))
573 if param:
589 if param:
574 yield param
590 yield param
575 for chunk in self._compengine.compressstream(self._getcorechunk(),
591 for chunk in self._compengine.compressstream(self._getcorechunk(),
576 self._compopts):
592 self._compopts):
577 yield chunk
593 yield chunk
578
594
579 def _paramchunk(self):
595 def _paramchunk(self):
580 """return a encoded version of all stream parameters"""
596 """return a encoded version of all stream parameters"""
581 blocks = []
597 blocks = []
582 for par, value in self._params:
598 for par, value in self._params:
583 par = urlreq.quote(par)
599 par = urlreq.quote(par)
584 if value is not None:
600 if value is not None:
585 value = urlreq.quote(value)
601 value = urlreq.quote(value)
586 par = '%s=%s' % (par, value)
602 par = '%s=%s' % (par, value)
587 blocks.append(par)
603 blocks.append(par)
588 return ' '.join(blocks)
604 return ' '.join(blocks)
589
605
590 def _getcorechunk(self):
606 def _getcorechunk(self):
591 """yield chunk for the core part of the bundle
607 """yield chunk for the core part of the bundle
592
608
593 (all but headers and parameters)"""
609 (all but headers and parameters)"""
594 outdebug(self.ui, 'start of parts')
610 outdebug(self.ui, 'start of parts')
595 for part in self._parts:
611 for part in self._parts:
596 outdebug(self.ui, 'bundle part: "%s"' % part.type)
612 outdebug(self.ui, 'bundle part: "%s"' % part.type)
597 for chunk in part.getchunks(ui=self.ui):
613 for chunk in part.getchunks(ui=self.ui):
598 yield chunk
614 yield chunk
599 outdebug(self.ui, 'end of bundle')
615 outdebug(self.ui, 'end of bundle')
600 yield _pack(_fpartheadersize, 0)
616 yield _pack(_fpartheadersize, 0)
601
617
602
618
603 def salvageoutput(self):
619 def salvageoutput(self):
604 """return a list with a copy of all output parts in the bundle
620 """return a list with a copy of all output parts in the bundle
605
621
606 This is meant to be used during error handling to make sure we preserve
622 This is meant to be used during error handling to make sure we preserve
607 server output"""
623 server output"""
608 salvaged = []
624 salvaged = []
609 for part in self._parts:
625 for part in self._parts:
610 if part.type.startswith('output'):
626 if part.type.startswith('output'):
611 salvaged.append(part.copy())
627 salvaged.append(part.copy())
612 return salvaged
628 return salvaged
613
629
614
630
615 class unpackermixin(object):
631 class unpackermixin(object):
616 """A mixin to extract bytes and struct data from a stream"""
632 """A mixin to extract bytes and struct data from a stream"""
617
633
618 def __init__(self, fp):
634 def __init__(self, fp):
619 self._fp = fp
635 self._fp = fp
620
636
621 def _unpack(self, format):
637 def _unpack(self, format):
622 """unpack this struct format from the stream
638 """unpack this struct format from the stream
623
639
624 This method is meant for internal usage by the bundle2 protocol only.
640 This method is meant for internal usage by the bundle2 protocol only.
625 They directly manipulate the low level stream including bundle2 level
641 They directly manipulate the low level stream including bundle2 level
626 instruction.
642 instruction.
627
643
628 Do not use it to implement higher-level logic or methods."""
644 Do not use it to implement higher-level logic or methods."""
629 data = self._readexact(struct.calcsize(format))
645 data = self._readexact(struct.calcsize(format))
630 return _unpack(format, data)
646 return _unpack(format, data)
631
647
632 def _readexact(self, size):
648 def _readexact(self, size):
633 """read exactly <size> bytes from the stream
649 """read exactly <size> bytes from the stream
634
650
635 This method is meant for internal usage by the bundle2 protocol only.
651 This method is meant for internal usage by the bundle2 protocol only.
636 They directly manipulate the low level stream including bundle2 level
652 They directly manipulate the low level stream including bundle2 level
637 instruction.
653 instruction.
638
654
639 Do not use it to implement higher-level logic or methods."""
655 Do not use it to implement higher-level logic or methods."""
640 return changegroup.readexactly(self._fp, size)
656 return changegroup.readexactly(self._fp, size)
641
657
642 def getunbundler(ui, fp, magicstring=None):
658 def getunbundler(ui, fp, magicstring=None):
643 """return a valid unbundler object for a given magicstring"""
659 """return a valid unbundler object for a given magicstring"""
644 if magicstring is None:
660 if magicstring is None:
645 magicstring = changegroup.readexactly(fp, 4)
661 magicstring = changegroup.readexactly(fp, 4)
646 magic, version = magicstring[0:2], magicstring[2:4]
662 magic, version = magicstring[0:2], magicstring[2:4]
647 if magic != 'HG':
663 if magic != 'HG':
648 raise error.Abort(_('not a Mercurial bundle'))
664 raise error.Abort(_('not a Mercurial bundle'))
649 unbundlerclass = formatmap.get(version)
665 unbundlerclass = formatmap.get(version)
650 if unbundlerclass is None:
666 if unbundlerclass is None:
651 raise error.Abort(_('unknown bundle version %s') % version)
667 raise error.Abort(_('unknown bundle version %s') % version)
652 unbundler = unbundlerclass(ui, fp)
668 unbundler = unbundlerclass(ui, fp)
653 indebug(ui, 'start processing of %s stream' % magicstring)
669 indebug(ui, 'start processing of %s stream' % magicstring)
654 return unbundler
670 return unbundler
655
671
656 class unbundle20(unpackermixin):
672 class unbundle20(unpackermixin):
657 """interpret a bundle2 stream
673 """interpret a bundle2 stream
658
674
659 This class is fed with a binary stream and yields parts through its
675 This class is fed with a binary stream and yields parts through its
660 `iterparts` methods."""
676 `iterparts` methods."""
661
677
662 _magicstring = 'HG20'
678 _magicstring = 'HG20'
663
679
664 def __init__(self, ui, fp):
680 def __init__(self, ui, fp):
665 """If header is specified, we do not read it out of the stream."""
681 """If header is specified, we do not read it out of the stream."""
666 self.ui = ui
682 self.ui = ui
667 self._compengine = util.compengines.forbundletype('UN')
683 self._compengine = util.compengines.forbundletype('UN')
668 self._compressed = None
684 self._compressed = None
669 super(unbundle20, self).__init__(fp)
685 super(unbundle20, self).__init__(fp)
670
686
671 @util.propertycache
687 @util.propertycache
672 def params(self):
688 def params(self):
673 """dictionary of stream level parameters"""
689 """dictionary of stream level parameters"""
674 indebug(self.ui, 'reading bundle2 stream parameters')
690 indebug(self.ui, 'reading bundle2 stream parameters')
675 params = {}
691 params = {}
676 paramssize = self._unpack(_fstreamparamsize)[0]
692 paramssize = self._unpack(_fstreamparamsize)[0]
677 if paramssize < 0:
693 if paramssize < 0:
678 raise error.BundleValueError('negative bundle param size: %i'
694 raise error.BundleValueError('negative bundle param size: %i'
679 % paramssize)
695 % paramssize)
680 if paramssize:
696 if paramssize:
681 params = self._readexact(paramssize)
697 params = self._readexact(paramssize)
682 params = self._processallparams(params)
698 params = self._processallparams(params)
683 return params
699 return params
684
700
685 def _processallparams(self, paramsblock):
701 def _processallparams(self, paramsblock):
686 """"""
702 """"""
687 params = util.sortdict()
703 params = util.sortdict()
688 for p in paramsblock.split(' '):
704 for p in paramsblock.split(' '):
689 p = p.split('=', 1)
705 p = p.split('=', 1)
690 p = [urlreq.unquote(i) for i in p]
706 p = [urlreq.unquote(i) for i in p]
691 if len(p) < 2:
707 if len(p) < 2:
692 p.append(None)
708 p.append(None)
693 self._processparam(*p)
709 self._processparam(*p)
694 params[p[0]] = p[1]
710 params[p[0]] = p[1]
695 return params
711 return params
696
712
697
713
698 def _processparam(self, name, value):
714 def _processparam(self, name, value):
699 """process a parameter, applying its effect if needed
715 """process a parameter, applying its effect if needed
700
716
701 Parameter starting with a lower case letter are advisory and will be
717 Parameter starting with a lower case letter are advisory and will be
702 ignored when unknown. Those starting with an upper case letter are
718 ignored when unknown. Those starting with an upper case letter are
703 mandatory and will this function will raise a KeyError when unknown.
719 mandatory and will this function will raise a KeyError when unknown.
704
720
705 Note: no option are currently supported. Any input will be either
721 Note: no option are currently supported. Any input will be either
706 ignored or failing.
722 ignored or failing.
707 """
723 """
708 if not name:
724 if not name:
709 raise ValueError('empty parameter name')
725 raise ValueError('empty parameter name')
710 if name[0] not in string.letters:
726 if name[0] not in string.letters:
711 raise ValueError('non letter first character: %r' % name)
727 raise ValueError('non letter first character: %r' % name)
712 try:
728 try:
713 handler = b2streamparamsmap[name.lower()]
729 handler = b2streamparamsmap[name.lower()]
714 except KeyError:
730 except KeyError:
715 if name[0].islower():
731 if name[0].islower():
716 indebug(self.ui, "ignoring unknown parameter %r" % name)
732 indebug(self.ui, "ignoring unknown parameter %r" % name)
717 else:
733 else:
718 raise error.BundleUnknownFeatureError(params=(name,))
734 raise error.BundleUnknownFeatureError(params=(name,))
719 else:
735 else:
720 handler(self, name, value)
736 handler(self, name, value)
721
737
722 def _forwardchunks(self):
738 def _forwardchunks(self):
723 """utility to transfer a bundle2 as binary
739 """utility to transfer a bundle2 as binary
724
740
725 This is made necessary by the fact the 'getbundle' command over 'ssh'
741 This is made necessary by the fact the 'getbundle' command over 'ssh'
726 have no way to know then the reply end, relying on the bundle to be
742 have no way to know then the reply end, relying on the bundle to be
727 interpreted to know its end. This is terrible and we are sorry, but we
743 interpreted to know its end. This is terrible and we are sorry, but we
728 needed to move forward to get general delta enabled.
744 needed to move forward to get general delta enabled.
729 """
745 """
730 yield self._magicstring
746 yield self._magicstring
731 assert 'params' not in vars(self)
747 assert 'params' not in vars(self)
732 paramssize = self._unpack(_fstreamparamsize)[0]
748 paramssize = self._unpack(_fstreamparamsize)[0]
733 if paramssize < 0:
749 if paramssize < 0:
734 raise error.BundleValueError('negative bundle param size: %i'
750 raise error.BundleValueError('negative bundle param size: %i'
735 % paramssize)
751 % paramssize)
736 yield _pack(_fstreamparamsize, paramssize)
752 yield _pack(_fstreamparamsize, paramssize)
737 if paramssize:
753 if paramssize:
738 params = self._readexact(paramssize)
754 params = self._readexact(paramssize)
739 self._processallparams(params)
755 self._processallparams(params)
740 yield params
756 yield params
741 assert self._compengine.bundletype == 'UN'
757 assert self._compengine.bundletype == 'UN'
742 # From there, payload might need to be decompressed
758 # From there, payload might need to be decompressed
743 self._fp = self._compengine.decompressorreader(self._fp)
759 self._fp = self._compengine.decompressorreader(self._fp)
744 emptycount = 0
760 emptycount = 0
745 while emptycount < 2:
761 while emptycount < 2:
746 # so we can brainlessly loop
762 # so we can brainlessly loop
747 assert _fpartheadersize == _fpayloadsize
763 assert _fpartheadersize == _fpayloadsize
748 size = self._unpack(_fpartheadersize)[0]
764 size = self._unpack(_fpartheadersize)[0]
749 yield _pack(_fpartheadersize, size)
765 yield _pack(_fpartheadersize, size)
750 if size:
766 if size:
751 emptycount = 0
767 emptycount = 0
752 else:
768 else:
753 emptycount += 1
769 emptycount += 1
754 continue
770 continue
755 if size == flaginterrupt:
771 if size == flaginterrupt:
756 continue
772 continue
757 elif size < 0:
773 elif size < 0:
758 raise error.BundleValueError('negative chunk size: %i')
774 raise error.BundleValueError('negative chunk size: %i')
759 yield self._readexact(size)
775 yield self._readexact(size)
760
776
761
777
762 def iterparts(self):
778 def iterparts(self):
763 """yield all parts contained in the stream"""
779 """yield all parts contained in the stream"""
764 # make sure param have been loaded
780 # make sure param have been loaded
765 self.params
781 self.params
766 # From there, payload need to be decompressed
782 # From there, payload need to be decompressed
767 self._fp = self._compengine.decompressorreader(self._fp)
783 self._fp = self._compengine.decompressorreader(self._fp)
768 indebug(self.ui, 'start extraction of bundle2 parts')
784 indebug(self.ui, 'start extraction of bundle2 parts')
769 headerblock = self._readpartheader()
785 headerblock = self._readpartheader()
770 while headerblock is not None:
786 while headerblock is not None:
771 part = unbundlepart(self.ui, headerblock, self._fp)
787 part = unbundlepart(self.ui, headerblock, self._fp)
772 yield part
788 yield part
773 part.seek(0, 2)
789 part.seek(0, 2)
774 headerblock = self._readpartheader()
790 headerblock = self._readpartheader()
775 indebug(self.ui, 'end of bundle2 stream')
791 indebug(self.ui, 'end of bundle2 stream')
776
792
777 def _readpartheader(self):
793 def _readpartheader(self):
778 """reads a part header size and return the bytes blob
794 """reads a part header size and return the bytes blob
779
795
780 returns None if empty"""
796 returns None if empty"""
781 headersize = self._unpack(_fpartheadersize)[0]
797 headersize = self._unpack(_fpartheadersize)[0]
782 if headersize < 0:
798 if headersize < 0:
783 raise error.BundleValueError('negative part header size: %i'
799 raise error.BundleValueError('negative part header size: %i'
784 % headersize)
800 % headersize)
785 indebug(self.ui, 'part header size: %i' % headersize)
801 indebug(self.ui, 'part header size: %i' % headersize)
786 if headersize:
802 if headersize:
787 return self._readexact(headersize)
803 return self._readexact(headersize)
788 return None
804 return None
789
805
790 def compressed(self):
806 def compressed(self):
791 self.params # load params
807 self.params # load params
792 return self._compressed
808 return self._compressed
793
809
794 def close(self):
810 def close(self):
795 """close underlying file"""
811 """close underlying file"""
796 if util.safehasattr(self._fp, 'close'):
812 if util.safehasattr(self._fp, 'close'):
797 return self._fp.close()
813 return self._fp.close()
798
814
799 formatmap = {'20': unbundle20}
815 formatmap = {'20': unbundle20}
800
816
801 b2streamparamsmap = {}
817 b2streamparamsmap = {}
802
818
803 def b2streamparamhandler(name):
819 def b2streamparamhandler(name):
804 """register a handler for a stream level parameter"""
820 """register a handler for a stream level parameter"""
805 def decorator(func):
821 def decorator(func):
806 assert name not in formatmap
822 assert name not in formatmap
807 b2streamparamsmap[name] = func
823 b2streamparamsmap[name] = func
808 return func
824 return func
809 return decorator
825 return decorator
810
826
811 @b2streamparamhandler('compression')
827 @b2streamparamhandler('compression')
812 def processcompression(unbundler, param, value):
828 def processcompression(unbundler, param, value):
813 """read compression parameter and install payload decompression"""
829 """read compression parameter and install payload decompression"""
814 if value not in util.compengines.supportedbundletypes:
830 if value not in util.compengines.supportedbundletypes:
815 raise error.BundleUnknownFeatureError(params=(param,),
831 raise error.BundleUnknownFeatureError(params=(param,),
816 values=(value,))
832 values=(value,))
817 unbundler._compengine = util.compengines.forbundletype(value)
833 unbundler._compengine = util.compengines.forbundletype(value)
818 if value is not None:
834 if value is not None:
819 unbundler._compressed = True
835 unbundler._compressed = True
820
836
821 class bundlepart(object):
837 class bundlepart(object):
822 """A bundle2 part contains application level payload
838 """A bundle2 part contains application level payload
823
839
824 The part `type` is used to route the part to the application level
840 The part `type` is used to route the part to the application level
825 handler.
841 handler.
826
842
827 The part payload is contained in ``part.data``. It could be raw bytes or a
843 The part payload is contained in ``part.data``. It could be raw bytes or a
828 generator of byte chunks.
844 generator of byte chunks.
829
845
830 You can add parameters to the part using the ``addparam`` method.
846 You can add parameters to the part using the ``addparam`` method.
831 Parameters can be either mandatory (default) or advisory. Remote side
847 Parameters can be either mandatory (default) or advisory. Remote side
832 should be able to safely ignore the advisory ones.
848 should be able to safely ignore the advisory ones.
833
849
834 Both data and parameters cannot be modified after the generation has begun.
850 Both data and parameters cannot be modified after the generation has begun.
835 """
851 """
836
852
837 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
853 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
838 data='', mandatory=True):
854 data='', mandatory=True):
839 validateparttype(parttype)
855 validateparttype(parttype)
840 self.id = None
856 self.id = None
841 self.type = parttype
857 self.type = parttype
842 self._data = data
858 self._data = data
843 self._mandatoryparams = list(mandatoryparams)
859 self._mandatoryparams = list(mandatoryparams)
844 self._advisoryparams = list(advisoryparams)
860 self._advisoryparams = list(advisoryparams)
845 # checking for duplicated entries
861 # checking for duplicated entries
846 self._seenparams = set()
862 self._seenparams = set()
847 for pname, __ in self._mandatoryparams + self._advisoryparams:
863 for pname, __ in self._mandatoryparams + self._advisoryparams:
848 if pname in self._seenparams:
864 if pname in self._seenparams:
849 raise error.ProgrammingError('duplicated params: %s' % pname)
865 raise error.ProgrammingError('duplicated params: %s' % pname)
850 self._seenparams.add(pname)
866 self._seenparams.add(pname)
851 # status of the part's generation:
867 # status of the part's generation:
852 # - None: not started,
868 # - None: not started,
853 # - False: currently generated,
869 # - False: currently generated,
854 # - True: generation done.
870 # - True: generation done.
855 self._generated = None
871 self._generated = None
856 self.mandatory = mandatory
872 self.mandatory = mandatory
857
873
858 def __repr__(self):
874 def __repr__(self):
859 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
875 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
860 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
876 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
861 % (cls, id(self), self.id, self.type, self.mandatory))
877 % (cls, id(self), self.id, self.type, self.mandatory))
862
878
863 def copy(self):
879 def copy(self):
864 """return a copy of the part
880 """return a copy of the part
865
881
866 The new part have the very same content but no partid assigned yet.
882 The new part have the very same content but no partid assigned yet.
867 Parts with generated data cannot be copied."""
883 Parts with generated data cannot be copied."""
868 assert not util.safehasattr(self.data, 'next')
884 assert not util.safehasattr(self.data, 'next')
869 return self.__class__(self.type, self._mandatoryparams,
885 return self.__class__(self.type, self._mandatoryparams,
870 self._advisoryparams, self._data, self.mandatory)
886 self._advisoryparams, self._data, self.mandatory)
871
887
872 # methods used to defines the part content
888 # methods used to defines the part content
873 @property
889 @property
874 def data(self):
890 def data(self):
875 return self._data
891 return self._data
876
892
877 @data.setter
893 @data.setter
878 def data(self, data):
894 def data(self, data):
879 if self._generated is not None:
895 if self._generated is not None:
880 raise error.ReadOnlyPartError('part is being generated')
896 raise error.ReadOnlyPartError('part is being generated')
881 self._data = data
897 self._data = data
882
898
883 @property
899 @property
884 def mandatoryparams(self):
900 def mandatoryparams(self):
885 # make it an immutable tuple to force people through ``addparam``
901 # make it an immutable tuple to force people through ``addparam``
886 return tuple(self._mandatoryparams)
902 return tuple(self._mandatoryparams)
887
903
888 @property
904 @property
889 def advisoryparams(self):
905 def advisoryparams(self):
890 # make it an immutable tuple to force people through ``addparam``
906 # make it an immutable tuple to force people through ``addparam``
891 return tuple(self._advisoryparams)
907 return tuple(self._advisoryparams)
892
908
893 def addparam(self, name, value='', mandatory=True):
909 def addparam(self, name, value='', mandatory=True):
894 """add a parameter to the part
910 """add a parameter to the part
895
911
896 If 'mandatory' is set to True, the remote handler must claim support
912 If 'mandatory' is set to True, the remote handler must claim support
897 for this parameter or the unbundling will be aborted.
913 for this parameter or the unbundling will be aborted.
898
914
899 The 'name' and 'value' cannot exceed 255 bytes each.
915 The 'name' and 'value' cannot exceed 255 bytes each.
900 """
916 """
901 if self._generated is not None:
917 if self._generated is not None:
902 raise error.ReadOnlyPartError('part is being generated')
918 raise error.ReadOnlyPartError('part is being generated')
903 if name in self._seenparams:
919 if name in self._seenparams:
904 raise ValueError('duplicated params: %s' % name)
920 raise ValueError('duplicated params: %s' % name)
905 self._seenparams.add(name)
921 self._seenparams.add(name)
906 params = self._advisoryparams
922 params = self._advisoryparams
907 if mandatory:
923 if mandatory:
908 params = self._mandatoryparams
924 params = self._mandatoryparams
909 params.append((name, value))
925 params.append((name, value))
910
926
911 # methods used to generates the bundle2 stream
927 # methods used to generates the bundle2 stream
912 def getchunks(self, ui):
928 def getchunks(self, ui):
913 if self._generated is not None:
929 if self._generated is not None:
914 raise error.ProgrammingError('part can only be consumed once')
930 raise error.ProgrammingError('part can only be consumed once')
915 self._generated = False
931 self._generated = False
916
932
917 if ui.debugflag:
933 if ui.debugflag:
918 msg = ['bundle2-output-part: "%s"' % self.type]
934 msg = ['bundle2-output-part: "%s"' % self.type]
919 if not self.mandatory:
935 if not self.mandatory:
920 msg.append(' (advisory)')
936 msg.append(' (advisory)')
921 nbmp = len(self.mandatoryparams)
937 nbmp = len(self.mandatoryparams)
922 nbap = len(self.advisoryparams)
938 nbap = len(self.advisoryparams)
923 if nbmp or nbap:
939 if nbmp or nbap:
924 msg.append(' (params:')
940 msg.append(' (params:')
925 if nbmp:
941 if nbmp:
926 msg.append(' %i mandatory' % nbmp)
942 msg.append(' %i mandatory' % nbmp)
927 if nbap:
943 if nbap:
928 msg.append(' %i advisory' % nbmp)
944 msg.append(' %i advisory' % nbmp)
929 msg.append(')')
945 msg.append(')')
930 if not self.data:
946 if not self.data:
931 msg.append(' empty payload')
947 msg.append(' empty payload')
932 elif util.safehasattr(self.data, 'next'):
948 elif util.safehasattr(self.data, 'next'):
933 msg.append(' streamed payload')
949 msg.append(' streamed payload')
934 else:
950 else:
935 msg.append(' %i bytes payload' % len(self.data))
951 msg.append(' %i bytes payload' % len(self.data))
936 msg.append('\n')
952 msg.append('\n')
937 ui.debug(''.join(msg))
953 ui.debug(''.join(msg))
938
954
939 #### header
955 #### header
940 if self.mandatory:
956 if self.mandatory:
941 parttype = self.type.upper()
957 parttype = self.type.upper()
942 else:
958 else:
943 parttype = self.type.lower()
959 parttype = self.type.lower()
944 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
960 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
945 ## parttype
961 ## parttype
946 header = [_pack(_fparttypesize, len(parttype)),
962 header = [_pack(_fparttypesize, len(parttype)),
947 parttype, _pack(_fpartid, self.id),
963 parttype, _pack(_fpartid, self.id),
948 ]
964 ]
949 ## parameters
965 ## parameters
950 # count
966 # count
951 manpar = self.mandatoryparams
967 manpar = self.mandatoryparams
952 advpar = self.advisoryparams
968 advpar = self.advisoryparams
953 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
969 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
954 # size
970 # size
955 parsizes = []
971 parsizes = []
956 for key, value in manpar:
972 for key, value in manpar:
957 parsizes.append(len(key))
973 parsizes.append(len(key))
958 parsizes.append(len(value))
974 parsizes.append(len(value))
959 for key, value in advpar:
975 for key, value in advpar:
960 parsizes.append(len(key))
976 parsizes.append(len(key))
961 parsizes.append(len(value))
977 parsizes.append(len(value))
962 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
978 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
963 header.append(paramsizes)
979 header.append(paramsizes)
964 # key, value
980 # key, value
965 for key, value in manpar:
981 for key, value in manpar:
966 header.append(key)
982 header.append(key)
967 header.append(value)
983 header.append(value)
968 for key, value in advpar:
984 for key, value in advpar:
969 header.append(key)
985 header.append(key)
970 header.append(value)
986 header.append(value)
971 ## finalize header
987 ## finalize header
972 headerchunk = ''.join(header)
988 headerchunk = ''.join(header)
973 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
989 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
974 yield _pack(_fpartheadersize, len(headerchunk))
990 yield _pack(_fpartheadersize, len(headerchunk))
975 yield headerchunk
991 yield headerchunk
976 ## payload
992 ## payload
977 try:
993 try:
978 for chunk in self._payloadchunks():
994 for chunk in self._payloadchunks():
979 outdebug(ui, 'payload chunk size: %i' % len(chunk))
995 outdebug(ui, 'payload chunk size: %i' % len(chunk))
980 yield _pack(_fpayloadsize, len(chunk))
996 yield _pack(_fpayloadsize, len(chunk))
981 yield chunk
997 yield chunk
982 except GeneratorExit:
998 except GeneratorExit:
983 # GeneratorExit means that nobody is listening for our
999 # GeneratorExit means that nobody is listening for our
984 # results anyway, so just bail quickly rather than trying
1000 # results anyway, so just bail quickly rather than trying
985 # to produce an error part.
1001 # to produce an error part.
986 ui.debug('bundle2-generatorexit\n')
1002 ui.debug('bundle2-generatorexit\n')
987 raise
1003 raise
988 except BaseException as exc:
1004 except BaseException as exc:
989 # backup exception data for later
1005 # backup exception data for later
990 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1006 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
991 % exc)
1007 % exc)
992 exc_info = sys.exc_info()
1008 exc_info = sys.exc_info()
993 msg = 'unexpected error: %s' % exc
1009 msg = 'unexpected error: %s' % exc
994 interpart = bundlepart('error:abort', [('message', msg)],
1010 interpart = bundlepart('error:abort', [('message', msg)],
995 mandatory=False)
1011 mandatory=False)
996 interpart.id = 0
1012 interpart.id = 0
997 yield _pack(_fpayloadsize, -1)
1013 yield _pack(_fpayloadsize, -1)
998 for chunk in interpart.getchunks(ui=ui):
1014 for chunk in interpart.getchunks(ui=ui):
999 yield chunk
1015 yield chunk
1000 outdebug(ui, 'closing payload chunk')
1016 outdebug(ui, 'closing payload chunk')
1001 # abort current part payload
1017 # abort current part payload
1002 yield _pack(_fpayloadsize, 0)
1018 yield _pack(_fpayloadsize, 0)
1003 if pycompat.ispy3:
1019 if pycompat.ispy3:
1004 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
1020 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
1005 else:
1021 else:
1006 exec("""raise exc_info[0], exc_info[1], exc_info[2]""")
1022 exec("""raise exc_info[0], exc_info[1], exc_info[2]""")
1007 # end of payload
1023 # end of payload
1008 outdebug(ui, 'closing payload chunk')
1024 outdebug(ui, 'closing payload chunk')
1009 yield _pack(_fpayloadsize, 0)
1025 yield _pack(_fpayloadsize, 0)
1010 self._generated = True
1026 self._generated = True
1011
1027
1012 def _payloadchunks(self):
1028 def _payloadchunks(self):
1013 """yield chunks of a the part payload
1029 """yield chunks of a the part payload
1014
1030
1015 Exists to handle the different methods to provide data to a part."""
1031 Exists to handle the different methods to provide data to a part."""
1016 # we only support fixed size data now.
1032 # we only support fixed size data now.
1017 # This will be improved in the future.
1033 # This will be improved in the future.
1018 if util.safehasattr(self.data, 'next'):
1034 if util.safehasattr(self.data, 'next'):
1019 buff = util.chunkbuffer(self.data)
1035 buff = util.chunkbuffer(self.data)
1020 chunk = buff.read(preferedchunksize)
1036 chunk = buff.read(preferedchunksize)
1021 while chunk:
1037 while chunk:
1022 yield chunk
1038 yield chunk
1023 chunk = buff.read(preferedchunksize)
1039 chunk = buff.read(preferedchunksize)
1024 elif len(self.data):
1040 elif len(self.data):
1025 yield self.data
1041 yield self.data
1026
1042
1027
1043
1028 flaginterrupt = -1
1044 flaginterrupt = -1
1029
1045
1030 class interrupthandler(unpackermixin):
1046 class interrupthandler(unpackermixin):
1031 """read one part and process it with restricted capability
1047 """read one part and process it with restricted capability
1032
1048
1033 This allows to transmit exception raised on the producer size during part
1049 This allows to transmit exception raised on the producer size during part
1034 iteration while the consumer is reading a part.
1050 iteration while the consumer is reading a part.
1035
1051
1036 Part processed in this manner only have access to a ui object,"""
1052 Part processed in this manner only have access to a ui object,"""
1037
1053
1038 def __init__(self, ui, fp):
1054 def __init__(self, ui, fp):
1039 super(interrupthandler, self).__init__(fp)
1055 super(interrupthandler, self).__init__(fp)
1040 self.ui = ui
1056 self.ui = ui
1041
1057
1042 def _readpartheader(self):
1058 def _readpartheader(self):
1043 """reads a part header size and return the bytes blob
1059 """reads a part header size and return the bytes blob
1044
1060
1045 returns None if empty"""
1061 returns None if empty"""
1046 headersize = self._unpack(_fpartheadersize)[0]
1062 headersize = self._unpack(_fpartheadersize)[0]
1047 if headersize < 0:
1063 if headersize < 0:
1048 raise error.BundleValueError('negative part header size: %i'
1064 raise error.BundleValueError('negative part header size: %i'
1049 % headersize)
1065 % headersize)
1050 indebug(self.ui, 'part header size: %i\n' % headersize)
1066 indebug(self.ui, 'part header size: %i\n' % headersize)
1051 if headersize:
1067 if headersize:
1052 return self._readexact(headersize)
1068 return self._readexact(headersize)
1053 return None
1069 return None
1054
1070
1055 def __call__(self):
1071 def __call__(self):
1056
1072
1057 self.ui.debug('bundle2-input-stream-interrupt:'
1073 self.ui.debug('bundle2-input-stream-interrupt:'
1058 ' opening out of band context\n')
1074 ' opening out of band context\n')
1059 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1075 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1060 headerblock = self._readpartheader()
1076 headerblock = self._readpartheader()
1061 if headerblock is None:
1077 if headerblock is None:
1062 indebug(self.ui, 'no part found during interruption.')
1078 indebug(self.ui, 'no part found during interruption.')
1063 return
1079 return
1064 part = unbundlepart(self.ui, headerblock, self._fp)
1080 part = unbundlepart(self.ui, headerblock, self._fp)
1065 op = interruptoperation(self.ui)
1081 op = interruptoperation(self.ui)
1066 _processpart(op, part)
1082 _processpart(op, part)
1067 self.ui.debug('bundle2-input-stream-interrupt:'
1083 self.ui.debug('bundle2-input-stream-interrupt:'
1068 ' closing out of band context\n')
1084 ' closing out of band context\n')
1069
1085
1070 class interruptoperation(object):
1086 class interruptoperation(object):
1071 """A limited operation to be use by part handler during interruption
1087 """A limited operation to be use by part handler during interruption
1072
1088
1073 It only have access to an ui object.
1089 It only have access to an ui object.
1074 """
1090 """
1075
1091
1076 def __init__(self, ui):
1092 def __init__(self, ui):
1077 self.ui = ui
1093 self.ui = ui
1078 self.reply = None
1094 self.reply = None
1079 self.captureoutput = False
1095 self.captureoutput = False
1080
1096
1081 @property
1097 @property
1082 def repo(self):
1098 def repo(self):
1083 raise error.ProgrammingError('no repo access from stream interruption')
1099 raise error.ProgrammingError('no repo access from stream interruption')
1084
1100
1085 def gettransaction(self):
1101 def gettransaction(self):
1086 raise TransactionUnavailable('no repo access from stream interruption')
1102 raise TransactionUnavailable('no repo access from stream interruption')
1087
1103
1088 class unbundlepart(unpackermixin):
1104 class unbundlepart(unpackermixin):
1089 """a bundle part read from a bundle"""
1105 """a bundle part read from a bundle"""
1090
1106
1091 def __init__(self, ui, header, fp):
1107 def __init__(self, ui, header, fp):
1092 super(unbundlepart, self).__init__(fp)
1108 super(unbundlepart, self).__init__(fp)
1093 self._seekable = (util.safehasattr(fp, 'seek') and
1109 self._seekable = (util.safehasattr(fp, 'seek') and
1094 util.safehasattr(fp, 'tell'))
1110 util.safehasattr(fp, 'tell'))
1095 self.ui = ui
1111 self.ui = ui
1096 # unbundle state attr
1112 # unbundle state attr
1097 self._headerdata = header
1113 self._headerdata = header
1098 self._headeroffset = 0
1114 self._headeroffset = 0
1099 self._initialized = False
1115 self._initialized = False
1100 self.consumed = False
1116 self.consumed = False
1101 # part data
1117 # part data
1102 self.id = None
1118 self.id = None
1103 self.type = None
1119 self.type = None
1104 self.mandatoryparams = None
1120 self.mandatoryparams = None
1105 self.advisoryparams = None
1121 self.advisoryparams = None
1106 self.params = None
1122 self.params = None
1107 self.mandatorykeys = ()
1123 self.mandatorykeys = ()
1108 self._payloadstream = None
1124 self._payloadstream = None
1109 self._readheader()
1125 self._readheader()
1110 self._mandatory = None
1126 self._mandatory = None
1111 self._chunkindex = [] #(payload, file) position tuples for chunk starts
1127 self._chunkindex = [] #(payload, file) position tuples for chunk starts
1112 self._pos = 0
1128 self._pos = 0
1113
1129
1114 def _fromheader(self, size):
1130 def _fromheader(self, size):
1115 """return the next <size> byte from the header"""
1131 """return the next <size> byte from the header"""
1116 offset = self._headeroffset
1132 offset = self._headeroffset
1117 data = self._headerdata[offset:(offset + size)]
1133 data = self._headerdata[offset:(offset + size)]
1118 self._headeroffset = offset + size
1134 self._headeroffset = offset + size
1119 return data
1135 return data
1120
1136
1121 def _unpackheader(self, format):
1137 def _unpackheader(self, format):
1122 """read given format from header
1138 """read given format from header
1123
1139
1124 This automatically compute the size of the format to read."""
1140 This automatically compute the size of the format to read."""
1125 data = self._fromheader(struct.calcsize(format))
1141 data = self._fromheader(struct.calcsize(format))
1126 return _unpack(format, data)
1142 return _unpack(format, data)
1127
1143
1128 def _initparams(self, mandatoryparams, advisoryparams):
1144 def _initparams(self, mandatoryparams, advisoryparams):
1129 """internal function to setup all logic related parameters"""
1145 """internal function to setup all logic related parameters"""
1130 # make it read only to prevent people touching it by mistake.
1146 # make it read only to prevent people touching it by mistake.
1131 self.mandatoryparams = tuple(mandatoryparams)
1147 self.mandatoryparams = tuple(mandatoryparams)
1132 self.advisoryparams = tuple(advisoryparams)
1148 self.advisoryparams = tuple(advisoryparams)
1133 # user friendly UI
1149 # user friendly UI
1134 self.params = util.sortdict(self.mandatoryparams)
1150 self.params = util.sortdict(self.mandatoryparams)
1135 self.params.update(self.advisoryparams)
1151 self.params.update(self.advisoryparams)
1136 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1152 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1137
1153
1138 def _payloadchunks(self, chunknum=0):
1154 def _payloadchunks(self, chunknum=0):
1139 '''seek to specified chunk and start yielding data'''
1155 '''seek to specified chunk and start yielding data'''
1140 if len(self._chunkindex) == 0:
1156 if len(self._chunkindex) == 0:
1141 assert chunknum == 0, 'Must start with chunk 0'
1157 assert chunknum == 0, 'Must start with chunk 0'
1142 self._chunkindex.append((0, self._tellfp()))
1158 self._chunkindex.append((0, self._tellfp()))
1143 else:
1159 else:
1144 assert chunknum < len(self._chunkindex), \
1160 assert chunknum < len(self._chunkindex), \
1145 'Unknown chunk %d' % chunknum
1161 'Unknown chunk %d' % chunknum
1146 self._seekfp(self._chunkindex[chunknum][1])
1162 self._seekfp(self._chunkindex[chunknum][1])
1147
1163
1148 pos = self._chunkindex[chunknum][0]
1164 pos = self._chunkindex[chunknum][0]
1149 payloadsize = self._unpack(_fpayloadsize)[0]
1165 payloadsize = self._unpack(_fpayloadsize)[0]
1150 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1166 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1151 while payloadsize:
1167 while payloadsize:
1152 if payloadsize == flaginterrupt:
1168 if payloadsize == flaginterrupt:
1153 # interruption detection, the handler will now read a
1169 # interruption detection, the handler will now read a
1154 # single part and process it.
1170 # single part and process it.
1155 interrupthandler(self.ui, self._fp)()
1171 interrupthandler(self.ui, self._fp)()
1156 elif payloadsize < 0:
1172 elif payloadsize < 0:
1157 msg = 'negative payload chunk size: %i' % payloadsize
1173 msg = 'negative payload chunk size: %i' % payloadsize
1158 raise error.BundleValueError(msg)
1174 raise error.BundleValueError(msg)
1159 else:
1175 else:
1160 result = self._readexact(payloadsize)
1176 result = self._readexact(payloadsize)
1161 chunknum += 1
1177 chunknum += 1
1162 pos += payloadsize
1178 pos += payloadsize
1163 if chunknum == len(self._chunkindex):
1179 if chunknum == len(self._chunkindex):
1164 self._chunkindex.append((pos, self._tellfp()))
1180 self._chunkindex.append((pos, self._tellfp()))
1165 yield result
1181 yield result
1166 payloadsize = self._unpack(_fpayloadsize)[0]
1182 payloadsize = self._unpack(_fpayloadsize)[0]
1167 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1183 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1168
1184
1169 def _findchunk(self, pos):
1185 def _findchunk(self, pos):
1170 '''for a given payload position, return a chunk number and offset'''
1186 '''for a given payload position, return a chunk number and offset'''
1171 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1187 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1172 if ppos == pos:
1188 if ppos == pos:
1173 return chunk, 0
1189 return chunk, 0
1174 elif ppos > pos:
1190 elif ppos > pos:
1175 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1191 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1176 raise ValueError('Unknown chunk')
1192 raise ValueError('Unknown chunk')
1177
1193
1178 def _readheader(self):
1194 def _readheader(self):
1179 """read the header and setup the object"""
1195 """read the header and setup the object"""
1180 typesize = self._unpackheader(_fparttypesize)[0]
1196 typesize = self._unpackheader(_fparttypesize)[0]
1181 self.type = self._fromheader(typesize)
1197 self.type = self._fromheader(typesize)
1182 indebug(self.ui, 'part type: "%s"' % self.type)
1198 indebug(self.ui, 'part type: "%s"' % self.type)
1183 self.id = self._unpackheader(_fpartid)[0]
1199 self.id = self._unpackheader(_fpartid)[0]
1184 indebug(self.ui, 'part id: "%s"' % self.id)
1200 indebug(self.ui, 'part id: "%s"' % self.id)
1185 # extract mandatory bit from type
1201 # extract mandatory bit from type
1186 self.mandatory = (self.type != self.type.lower())
1202 self.mandatory = (self.type != self.type.lower())
1187 self.type = self.type.lower()
1203 self.type = self.type.lower()
1188 ## reading parameters
1204 ## reading parameters
1189 # param count
1205 # param count
1190 mancount, advcount = self._unpackheader(_fpartparamcount)
1206 mancount, advcount = self._unpackheader(_fpartparamcount)
1191 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1207 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1192 # param size
1208 # param size
1193 fparamsizes = _makefpartparamsizes(mancount + advcount)
1209 fparamsizes = _makefpartparamsizes(mancount + advcount)
1194 paramsizes = self._unpackheader(fparamsizes)
1210 paramsizes = self._unpackheader(fparamsizes)
1195 # make it a list of couple again
1211 # make it a list of couple again
1196 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1212 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1197 # split mandatory from advisory
1213 # split mandatory from advisory
1198 mansizes = paramsizes[:mancount]
1214 mansizes = paramsizes[:mancount]
1199 advsizes = paramsizes[mancount:]
1215 advsizes = paramsizes[mancount:]
1200 # retrieve param value
1216 # retrieve param value
1201 manparams = []
1217 manparams = []
1202 for key, value in mansizes:
1218 for key, value in mansizes:
1203 manparams.append((self._fromheader(key), self._fromheader(value)))
1219 manparams.append((self._fromheader(key), self._fromheader(value)))
1204 advparams = []
1220 advparams = []
1205 for key, value in advsizes:
1221 for key, value in advsizes:
1206 advparams.append((self._fromheader(key), self._fromheader(value)))
1222 advparams.append((self._fromheader(key), self._fromheader(value)))
1207 self._initparams(manparams, advparams)
1223 self._initparams(manparams, advparams)
1208 ## part payload
1224 ## part payload
1209 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1225 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1210 # we read the data, tell it
1226 # we read the data, tell it
1211 self._initialized = True
1227 self._initialized = True
1212
1228
1213 def read(self, size=None):
1229 def read(self, size=None):
1214 """read payload data"""
1230 """read payload data"""
1215 if not self._initialized:
1231 if not self._initialized:
1216 self._readheader()
1232 self._readheader()
1217 if size is None:
1233 if size is None:
1218 data = self._payloadstream.read()
1234 data = self._payloadstream.read()
1219 else:
1235 else:
1220 data = self._payloadstream.read(size)
1236 data = self._payloadstream.read(size)
1221 self._pos += len(data)
1237 self._pos += len(data)
1222 if size is None or len(data) < size:
1238 if size is None or len(data) < size:
1223 if not self.consumed and self._pos:
1239 if not self.consumed and self._pos:
1224 self.ui.debug('bundle2-input-part: total payload size %i\n'
1240 self.ui.debug('bundle2-input-part: total payload size %i\n'
1225 % self._pos)
1241 % self._pos)
1226 self.consumed = True
1242 self.consumed = True
1227 return data
1243 return data
1228
1244
1229 def tell(self):
1245 def tell(self):
1230 return self._pos
1246 return self._pos
1231
1247
1232 def seek(self, offset, whence=0):
1248 def seek(self, offset, whence=0):
1233 if whence == 0:
1249 if whence == 0:
1234 newpos = offset
1250 newpos = offset
1235 elif whence == 1:
1251 elif whence == 1:
1236 newpos = self._pos + offset
1252 newpos = self._pos + offset
1237 elif whence == 2:
1253 elif whence == 2:
1238 if not self.consumed:
1254 if not self.consumed:
1239 self.read()
1255 self.read()
1240 newpos = self._chunkindex[-1][0] - offset
1256 newpos = self._chunkindex[-1][0] - offset
1241 else:
1257 else:
1242 raise ValueError('Unknown whence value: %r' % (whence,))
1258 raise ValueError('Unknown whence value: %r' % (whence,))
1243
1259
1244 if newpos > self._chunkindex[-1][0] and not self.consumed:
1260 if newpos > self._chunkindex[-1][0] and not self.consumed:
1245 self.read()
1261 self.read()
1246 if not 0 <= newpos <= self._chunkindex[-1][0]:
1262 if not 0 <= newpos <= self._chunkindex[-1][0]:
1247 raise ValueError('Offset out of range')
1263 raise ValueError('Offset out of range')
1248
1264
1249 if self._pos != newpos:
1265 if self._pos != newpos:
1250 chunk, internaloffset = self._findchunk(newpos)
1266 chunk, internaloffset = self._findchunk(newpos)
1251 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1267 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1252 adjust = self.read(internaloffset)
1268 adjust = self.read(internaloffset)
1253 if len(adjust) != internaloffset:
1269 if len(adjust) != internaloffset:
1254 raise error.Abort(_('Seek failed\n'))
1270 raise error.Abort(_('Seek failed\n'))
1255 self._pos = newpos
1271 self._pos = newpos
1256
1272
1257 def _seekfp(self, offset, whence=0):
1273 def _seekfp(self, offset, whence=0):
1258 """move the underlying file pointer
1274 """move the underlying file pointer
1259
1275
1260 This method is meant for internal usage by the bundle2 protocol only.
1276 This method is meant for internal usage by the bundle2 protocol only.
1261 They directly manipulate the low level stream including bundle2 level
1277 They directly manipulate the low level stream including bundle2 level
1262 instruction.
1278 instruction.
1263
1279
1264 Do not use it to implement higher-level logic or methods."""
1280 Do not use it to implement higher-level logic or methods."""
1265 if self._seekable:
1281 if self._seekable:
1266 return self._fp.seek(offset, whence)
1282 return self._fp.seek(offset, whence)
1267 else:
1283 else:
1268 raise NotImplementedError(_('File pointer is not seekable'))
1284 raise NotImplementedError(_('File pointer is not seekable'))
1269
1285
1270 def _tellfp(self):
1286 def _tellfp(self):
1271 """return the file offset, or None if file is not seekable
1287 """return the file offset, or None if file is not seekable
1272
1288
1273 This method is meant for internal usage by the bundle2 protocol only.
1289 This method is meant for internal usage by the bundle2 protocol only.
1274 They directly manipulate the low level stream including bundle2 level
1290 They directly manipulate the low level stream including bundle2 level
1275 instruction.
1291 instruction.
1276
1292
1277 Do not use it to implement higher-level logic or methods."""
1293 Do not use it to implement higher-level logic or methods."""
1278 if self._seekable:
1294 if self._seekable:
1279 try:
1295 try:
1280 return self._fp.tell()
1296 return self._fp.tell()
1281 except IOError as e:
1297 except IOError as e:
1282 if e.errno == errno.ESPIPE:
1298 if e.errno == errno.ESPIPE:
1283 self._seekable = False
1299 self._seekable = False
1284 else:
1300 else:
1285 raise
1301 raise
1286 return None
1302 return None
1287
1303
1288 # These are only the static capabilities.
1304 # These are only the static capabilities.
1289 # Check the 'getrepocaps' function for the rest.
1305 # Check the 'getrepocaps' function for the rest.
1290 capabilities = {'HG20': (),
1306 capabilities = {'HG20': (),
1291 'error': ('abort', 'unsupportedcontent', 'pushraced',
1307 'error': ('abort', 'unsupportedcontent', 'pushraced',
1292 'pushkey'),
1308 'pushkey'),
1293 'listkeys': (),
1309 'listkeys': (),
1294 'pushkey': (),
1310 'pushkey': (),
1295 'digests': tuple(sorted(util.DIGESTS.keys())),
1311 'digests': tuple(sorted(util.DIGESTS.keys())),
1296 'remote-changegroup': ('http', 'https'),
1312 'remote-changegroup': ('http', 'https'),
1297 'hgtagsfnodes': (),
1313 'hgtagsfnodes': (),
1298 }
1314 }
1299
1315
1300 def getrepocaps(repo, allowpushback=False):
1316 def getrepocaps(repo, allowpushback=False):
1301 """return the bundle2 capabilities for a given repo
1317 """return the bundle2 capabilities for a given repo
1302
1318
1303 Exists to allow extensions (like evolution) to mutate the capabilities.
1319 Exists to allow extensions (like evolution) to mutate the capabilities.
1304 """
1320 """
1305 caps = capabilities.copy()
1321 caps = capabilities.copy()
1306 caps['changegroup'] = tuple(sorted(
1322 caps['changegroup'] = tuple(sorted(
1307 changegroup.supportedincomingversions(repo)))
1323 changegroup.supportedincomingversions(repo)))
1308 if obsolete.isenabled(repo, obsolete.exchangeopt):
1324 if obsolete.isenabled(repo, obsolete.exchangeopt):
1309 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1325 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1310 caps['obsmarkers'] = supportedformat
1326 caps['obsmarkers'] = supportedformat
1311 if allowpushback:
1327 if allowpushback:
1312 caps['pushback'] = ()
1328 caps['pushback'] = ()
1313 return caps
1329 return caps
1314
1330
1315 def bundle2caps(remote):
1331 def bundle2caps(remote):
1316 """return the bundle capabilities of a peer as dict"""
1332 """return the bundle capabilities of a peer as dict"""
1317 raw = remote.capable('bundle2')
1333 raw = remote.capable('bundle2')
1318 if not raw and raw != '':
1334 if not raw and raw != '':
1319 return {}
1335 return {}
1320 capsblob = urlreq.unquote(remote.capable('bundle2'))
1336 capsblob = urlreq.unquote(remote.capable('bundle2'))
1321 return decodecaps(capsblob)
1337 return decodecaps(capsblob)
1322
1338
1323 def obsmarkersversion(caps):
1339 def obsmarkersversion(caps):
1324 """extract the list of supported obsmarkers versions from a bundle2caps dict
1340 """extract the list of supported obsmarkers versions from a bundle2caps dict
1325 """
1341 """
1326 obscaps = caps.get('obsmarkers', ())
1342 obscaps = caps.get('obsmarkers', ())
1327 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1343 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1328
1344
1329 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1345 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1330 compopts=None):
1346 compopts=None):
1331 """Write a bundle file and return its filename.
1347 """Write a bundle file and return its filename.
1332
1348
1333 Existing files will not be overwritten.
1349 Existing files will not be overwritten.
1334 If no filename is specified, a temporary file is created.
1350 If no filename is specified, a temporary file is created.
1335 bz2 compression can be turned off.
1351 bz2 compression can be turned off.
1336 The bundle file will be deleted in case of errors.
1352 The bundle file will be deleted in case of errors.
1337 """
1353 """
1338
1354
1339 if bundletype == "HG20":
1355 if bundletype == "HG20":
1340 bundle = bundle20(ui)
1356 bundle = bundle20(ui)
1341 bundle.setcompression(compression, compopts)
1357 bundle.setcompression(compression, compopts)
1342 part = bundle.newpart('changegroup', data=cg.getchunks())
1358 part = bundle.newpart('changegroup', data=cg.getchunks())
1343 part.addparam('version', cg.version)
1359 part.addparam('version', cg.version)
1344 if 'clcount' in cg.extras:
1360 if 'clcount' in cg.extras:
1345 part.addparam('nbchanges', str(cg.extras['clcount']),
1361 part.addparam('nbchanges', str(cg.extras['clcount']),
1346 mandatory=False)
1362 mandatory=False)
1347 chunkiter = bundle.getchunks()
1363 chunkiter = bundle.getchunks()
1348 else:
1364 else:
1349 # compression argument is only for the bundle2 case
1365 # compression argument is only for the bundle2 case
1350 assert compression is None
1366 assert compression is None
1351 if cg.version != '01':
1367 if cg.version != '01':
1352 raise error.Abort(_('old bundle types only supports v1 '
1368 raise error.Abort(_('old bundle types only supports v1 '
1353 'changegroups'))
1369 'changegroups'))
1354 header, comp = bundletypes[bundletype]
1370 header, comp = bundletypes[bundletype]
1355 if comp not in util.compengines.supportedbundletypes:
1371 if comp not in util.compengines.supportedbundletypes:
1356 raise error.Abort(_('unknown stream compression type: %s')
1372 raise error.Abort(_('unknown stream compression type: %s')
1357 % comp)
1373 % comp)
1358 compengine = util.compengines.forbundletype(comp)
1374 compengine = util.compengines.forbundletype(comp)
1359 def chunkiter():
1375 def chunkiter():
1360 yield header
1376 yield header
1361 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1377 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1362 yield chunk
1378 yield chunk
1363 chunkiter = chunkiter()
1379 chunkiter = chunkiter()
1364
1380
1365 # parse the changegroup data, otherwise we will block
1381 # parse the changegroup data, otherwise we will block
1366 # in case of sshrepo because we don't know the end of the stream
1382 # in case of sshrepo because we don't know the end of the stream
1367 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1383 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1368
1384
1369 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest'))
1385 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest'))
1370 def handlechangegroup(op, inpart):
1386 def handlechangegroup(op, inpart):
1371 """apply a changegroup part on the repo
1387 """apply a changegroup part on the repo
1372
1388
1373 This is a very early implementation that will massive rework before being
1389 This is a very early implementation that will massive rework before being
1374 inflicted to any end-user.
1390 inflicted to any end-user.
1375 """
1391 """
1376 # Make sure we trigger a transaction creation
1392 # Make sure we trigger a transaction creation
1377 #
1393 #
1378 # The addchangegroup function will get a transaction object by itself, but
1394 # The addchangegroup function will get a transaction object by itself, but
1379 # we need to make sure we trigger the creation of a transaction object used
1395 # we need to make sure we trigger the creation of a transaction object used
1380 # for the whole processing scope.
1396 # for the whole processing scope.
1381 op.gettransaction()
1397 op.gettransaction()
1382 unpackerversion = inpart.params.get('version', '01')
1398 unpackerversion = inpart.params.get('version', '01')
1383 # We should raise an appropriate exception here
1399 # We should raise an appropriate exception here
1384 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1400 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1385 # the source and url passed here are overwritten by the one contained in
1401 # the source and url passed here are overwritten by the one contained in
1386 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1402 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1387 nbchangesets = None
1403 nbchangesets = None
1388 if 'nbchanges' in inpart.params:
1404 if 'nbchanges' in inpart.params:
1389 nbchangesets = int(inpart.params.get('nbchanges'))
1405 nbchangesets = int(inpart.params.get('nbchanges'))
1390 if ('treemanifest' in inpart.params and
1406 if ('treemanifest' in inpart.params and
1391 'treemanifest' not in op.repo.requirements):
1407 'treemanifest' not in op.repo.requirements):
1392 if len(op.repo.changelog) != 0:
1408 if len(op.repo.changelog) != 0:
1393 raise error.Abort(_(
1409 raise error.Abort(_(
1394 "bundle contains tree manifests, but local repo is "
1410 "bundle contains tree manifests, but local repo is "
1395 "non-empty and does not use tree manifests"))
1411 "non-empty and does not use tree manifests"))
1396 op.repo.requirements.add('treemanifest')
1412 op.repo.requirements.add('treemanifest')
1397 op.repo._applyopenerreqs()
1413 op.repo._applyopenerreqs()
1398 op.repo._writerequirements()
1414 op.repo._writerequirements()
1399 ret = cg.apply(op.repo, 'bundle2', 'bundle2', expectedtotal=nbchangesets)
1415 ret = cg.apply(op.repo, 'bundle2', 'bundle2', expectedtotal=nbchangesets)
1400 op.records.add('changegroup', {'return': ret})
1416 op.records.add('changegroup', {'return': ret})
1401 if op.reply is not None:
1417 if op.reply is not None:
1402 # This is definitely not the final form of this
1418 # This is definitely not the final form of this
1403 # return. But one need to start somewhere.
1419 # return. But one need to start somewhere.
1404 part = op.reply.newpart('reply:changegroup', mandatory=False)
1420 part = op.reply.newpart('reply:changegroup', mandatory=False)
1405 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1421 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1406 part.addparam('return', '%i' % ret, mandatory=False)
1422 part.addparam('return', '%i' % ret, mandatory=False)
1407 assert not inpart.read()
1423 assert not inpart.read()
1408
1424
1409 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1425 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1410 ['digest:%s' % k for k in util.DIGESTS.keys()])
1426 ['digest:%s' % k for k in util.DIGESTS.keys()])
1411 @parthandler('remote-changegroup', _remotechangegroupparams)
1427 @parthandler('remote-changegroup', _remotechangegroupparams)
1412 def handleremotechangegroup(op, inpart):
1428 def handleremotechangegroup(op, inpart):
1413 """apply a bundle10 on the repo, given an url and validation information
1429 """apply a bundle10 on the repo, given an url and validation information
1414
1430
1415 All the information about the remote bundle to import are given as
1431 All the information about the remote bundle to import are given as
1416 parameters. The parameters include:
1432 parameters. The parameters include:
1417 - url: the url to the bundle10.
1433 - url: the url to the bundle10.
1418 - size: the bundle10 file size. It is used to validate what was
1434 - size: the bundle10 file size. It is used to validate what was
1419 retrieved by the client matches the server knowledge about the bundle.
1435 retrieved by the client matches the server knowledge about the bundle.
1420 - digests: a space separated list of the digest types provided as
1436 - digests: a space separated list of the digest types provided as
1421 parameters.
1437 parameters.
1422 - digest:<digest-type>: the hexadecimal representation of the digest with
1438 - digest:<digest-type>: the hexadecimal representation of the digest with
1423 that name. Like the size, it is used to validate what was retrieved by
1439 that name. Like the size, it is used to validate what was retrieved by
1424 the client matches what the server knows about the bundle.
1440 the client matches what the server knows about the bundle.
1425
1441
1426 When multiple digest types are given, all of them are checked.
1442 When multiple digest types are given, all of them are checked.
1427 """
1443 """
1428 try:
1444 try:
1429 raw_url = inpart.params['url']
1445 raw_url = inpart.params['url']
1430 except KeyError:
1446 except KeyError:
1431 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1447 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1432 parsed_url = util.url(raw_url)
1448 parsed_url = util.url(raw_url)
1433 if parsed_url.scheme not in capabilities['remote-changegroup']:
1449 if parsed_url.scheme not in capabilities['remote-changegroup']:
1434 raise error.Abort(_('remote-changegroup does not support %s urls') %
1450 raise error.Abort(_('remote-changegroup does not support %s urls') %
1435 parsed_url.scheme)
1451 parsed_url.scheme)
1436
1452
1437 try:
1453 try:
1438 size = int(inpart.params['size'])
1454 size = int(inpart.params['size'])
1439 except ValueError:
1455 except ValueError:
1440 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1456 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1441 % 'size')
1457 % 'size')
1442 except KeyError:
1458 except KeyError:
1443 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1459 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1444
1460
1445 digests = {}
1461 digests = {}
1446 for typ in inpart.params.get('digests', '').split():
1462 for typ in inpart.params.get('digests', '').split():
1447 param = 'digest:%s' % typ
1463 param = 'digest:%s' % typ
1448 try:
1464 try:
1449 value = inpart.params[param]
1465 value = inpart.params[param]
1450 except KeyError:
1466 except KeyError:
1451 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1467 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1452 param)
1468 param)
1453 digests[typ] = value
1469 digests[typ] = value
1454
1470
1455 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1471 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1456
1472
1457 # Make sure we trigger a transaction creation
1473 # Make sure we trigger a transaction creation
1458 #
1474 #
1459 # The addchangegroup function will get a transaction object by itself, but
1475 # The addchangegroup function will get a transaction object by itself, but
1460 # we need to make sure we trigger the creation of a transaction object used
1476 # we need to make sure we trigger the creation of a transaction object used
1461 # for the whole processing scope.
1477 # for the whole processing scope.
1462 op.gettransaction()
1478 op.gettransaction()
1463 from . import exchange
1479 from . import exchange
1464 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1480 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1465 if not isinstance(cg, changegroup.cg1unpacker):
1481 if not isinstance(cg, changegroup.cg1unpacker):
1466 raise error.Abort(_('%s: not a bundle version 1.0') %
1482 raise error.Abort(_('%s: not a bundle version 1.0') %
1467 util.hidepassword(raw_url))
1483 util.hidepassword(raw_url))
1468 ret = cg.apply(op.repo, 'bundle2', 'bundle2')
1484 ret = cg.apply(op.repo, 'bundle2', 'bundle2')
1469 op.records.add('changegroup', {'return': ret})
1485 op.records.add('changegroup', {'return': ret})
1470 if op.reply is not None:
1486 if op.reply is not None:
1471 # This is definitely not the final form of this
1487 # This is definitely not the final form of this
1472 # return. But one need to start somewhere.
1488 # return. But one need to start somewhere.
1473 part = op.reply.newpart('reply:changegroup')
1489 part = op.reply.newpart('reply:changegroup')
1474 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1490 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1475 part.addparam('return', '%i' % ret, mandatory=False)
1491 part.addparam('return', '%i' % ret, mandatory=False)
1476 try:
1492 try:
1477 real_part.validate()
1493 real_part.validate()
1478 except error.Abort as e:
1494 except error.Abort as e:
1479 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1495 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1480 (util.hidepassword(raw_url), str(e)))
1496 (util.hidepassword(raw_url), str(e)))
1481 assert not inpart.read()
1497 assert not inpart.read()
1482
1498
1483 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1499 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1484 def handlereplychangegroup(op, inpart):
1500 def handlereplychangegroup(op, inpart):
1485 ret = int(inpart.params['return'])
1501 ret = int(inpart.params['return'])
1486 replyto = int(inpart.params['in-reply-to'])
1502 replyto = int(inpart.params['in-reply-to'])
1487 op.records.add('changegroup', {'return': ret}, replyto)
1503 op.records.add('changegroup', {'return': ret}, replyto)
1488
1504
1489 @parthandler('check:heads')
1505 @parthandler('check:heads')
1490 def handlecheckheads(op, inpart):
1506 def handlecheckheads(op, inpart):
1491 """check that head of the repo did not change
1507 """check that head of the repo did not change
1492
1508
1493 This is used to detect a push race when using unbundle.
1509 This is used to detect a push race when using unbundle.
1494 This replaces the "heads" argument of unbundle."""
1510 This replaces the "heads" argument of unbundle."""
1495 h = inpart.read(20)
1511 h = inpart.read(20)
1496 heads = []
1512 heads = []
1497 while len(h) == 20:
1513 while len(h) == 20:
1498 heads.append(h)
1514 heads.append(h)
1499 h = inpart.read(20)
1515 h = inpart.read(20)
1500 assert not h
1516 assert not h
1501 # Trigger a transaction so that we are guaranteed to have the lock now.
1517 # Trigger a transaction so that we are guaranteed to have the lock now.
1502 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1518 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1503 op.gettransaction()
1519 op.gettransaction()
1504 if sorted(heads) != sorted(op.repo.heads()):
1520 if sorted(heads) != sorted(op.repo.heads()):
1505 raise error.PushRaced('repository changed while pushing - '
1521 raise error.PushRaced('repository changed while pushing - '
1506 'please try again')
1522 'please try again')
1507
1523
1508 @parthandler('output')
1524 @parthandler('output')
1509 def handleoutput(op, inpart):
1525 def handleoutput(op, inpart):
1510 """forward output captured on the server to the client"""
1526 """forward output captured on the server to the client"""
1511 for line in inpart.read().splitlines():
1527 for line in inpart.read().splitlines():
1512 op.ui.status(_('remote: %s\n') % line)
1528 op.ui.status(_('remote: %s\n') % line)
1513
1529
1514 @parthandler('replycaps')
1530 @parthandler('replycaps')
1515 def handlereplycaps(op, inpart):
1531 def handlereplycaps(op, inpart):
1516 """Notify that a reply bundle should be created
1532 """Notify that a reply bundle should be created
1517
1533
1518 The payload contains the capabilities information for the reply"""
1534 The payload contains the capabilities information for the reply"""
1519 caps = decodecaps(inpart.read())
1535 caps = decodecaps(inpart.read())
1520 if op.reply is None:
1536 if op.reply is None:
1521 op.reply = bundle20(op.ui, caps)
1537 op.reply = bundle20(op.ui, caps)
1522
1538
1523 class AbortFromPart(error.Abort):
1539 class AbortFromPart(error.Abort):
1524 """Sub-class of Abort that denotes an error from a bundle2 part."""
1540 """Sub-class of Abort that denotes an error from a bundle2 part."""
1525
1541
1526 @parthandler('error:abort', ('message', 'hint'))
1542 @parthandler('error:abort', ('message', 'hint'))
1527 def handleerrorabort(op, inpart):
1543 def handleerrorabort(op, inpart):
1528 """Used to transmit abort error over the wire"""
1544 """Used to transmit abort error over the wire"""
1529 raise AbortFromPart(inpart.params['message'],
1545 raise AbortFromPart(inpart.params['message'],
1530 hint=inpart.params.get('hint'))
1546 hint=inpart.params.get('hint'))
1531
1547
1532 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
1548 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
1533 'in-reply-to'))
1549 'in-reply-to'))
1534 def handleerrorpushkey(op, inpart):
1550 def handleerrorpushkey(op, inpart):
1535 """Used to transmit failure of a mandatory pushkey over the wire"""
1551 """Used to transmit failure of a mandatory pushkey over the wire"""
1536 kwargs = {}
1552 kwargs = {}
1537 for name in ('namespace', 'key', 'new', 'old', 'ret'):
1553 for name in ('namespace', 'key', 'new', 'old', 'ret'):
1538 value = inpart.params.get(name)
1554 value = inpart.params.get(name)
1539 if value is not None:
1555 if value is not None:
1540 kwargs[name] = value
1556 kwargs[name] = value
1541 raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
1557 raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
1542
1558
1543 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1559 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1544 def handleerrorunsupportedcontent(op, inpart):
1560 def handleerrorunsupportedcontent(op, inpart):
1545 """Used to transmit unknown content error over the wire"""
1561 """Used to transmit unknown content error over the wire"""
1546 kwargs = {}
1562 kwargs = {}
1547 parttype = inpart.params.get('parttype')
1563 parttype = inpart.params.get('parttype')
1548 if parttype is not None:
1564 if parttype is not None:
1549 kwargs['parttype'] = parttype
1565 kwargs['parttype'] = parttype
1550 params = inpart.params.get('params')
1566 params = inpart.params.get('params')
1551 if params is not None:
1567 if params is not None:
1552 kwargs['params'] = params.split('\0')
1568 kwargs['params'] = params.split('\0')
1553
1569
1554 raise error.BundleUnknownFeatureError(**kwargs)
1570 raise error.BundleUnknownFeatureError(**kwargs)
1555
1571
1556 @parthandler('error:pushraced', ('message',))
1572 @parthandler('error:pushraced', ('message',))
1557 def handleerrorpushraced(op, inpart):
1573 def handleerrorpushraced(op, inpart):
1558 """Used to transmit push race error over the wire"""
1574 """Used to transmit push race error over the wire"""
1559 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1575 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1560
1576
1561 @parthandler('listkeys', ('namespace',))
1577 @parthandler('listkeys', ('namespace',))
1562 def handlelistkeys(op, inpart):
1578 def handlelistkeys(op, inpart):
1563 """retrieve pushkey namespace content stored in a bundle2"""
1579 """retrieve pushkey namespace content stored in a bundle2"""
1564 namespace = inpart.params['namespace']
1580 namespace = inpart.params['namespace']
1565 r = pushkey.decodekeys(inpart.read())
1581 r = pushkey.decodekeys(inpart.read())
1566 op.records.add('listkeys', (namespace, r))
1582 op.records.add('listkeys', (namespace, r))
1567
1583
1568 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1584 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1569 def handlepushkey(op, inpart):
1585 def handlepushkey(op, inpart):
1570 """process a pushkey request"""
1586 """process a pushkey request"""
1571 dec = pushkey.decode
1587 dec = pushkey.decode
1572 namespace = dec(inpart.params['namespace'])
1588 namespace = dec(inpart.params['namespace'])
1573 key = dec(inpart.params['key'])
1589 key = dec(inpart.params['key'])
1574 old = dec(inpart.params['old'])
1590 old = dec(inpart.params['old'])
1575 new = dec(inpart.params['new'])
1591 new = dec(inpart.params['new'])
1576 # Grab the transaction to ensure that we have the lock before performing the
1592 # Grab the transaction to ensure that we have the lock before performing the
1577 # pushkey.
1593 # pushkey.
1578 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1594 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1579 op.gettransaction()
1595 op.gettransaction()
1580 ret = op.repo.pushkey(namespace, key, old, new)
1596 ret = op.repo.pushkey(namespace, key, old, new)
1581 record = {'namespace': namespace,
1597 record = {'namespace': namespace,
1582 'key': key,
1598 'key': key,
1583 'old': old,
1599 'old': old,
1584 'new': new}
1600 'new': new}
1585 op.records.add('pushkey', record)
1601 op.records.add('pushkey', record)
1586 if op.reply is not None:
1602 if op.reply is not None:
1587 rpart = op.reply.newpart('reply:pushkey')
1603 rpart = op.reply.newpart('reply:pushkey')
1588 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1604 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1589 rpart.addparam('return', '%i' % ret, mandatory=False)
1605 rpart.addparam('return', '%i' % ret, mandatory=False)
1590 if inpart.mandatory and not ret:
1606 if inpart.mandatory and not ret:
1591 kwargs = {}
1607 kwargs = {}
1592 for key in ('namespace', 'key', 'new', 'old', 'ret'):
1608 for key in ('namespace', 'key', 'new', 'old', 'ret'):
1593 if key in inpart.params:
1609 if key in inpart.params:
1594 kwargs[key] = inpart.params[key]
1610 kwargs[key] = inpart.params[key]
1595 raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
1611 raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
1596
1612
1597 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1613 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1598 def handlepushkeyreply(op, inpart):
1614 def handlepushkeyreply(op, inpart):
1599 """retrieve the result of a pushkey request"""
1615 """retrieve the result of a pushkey request"""
1600 ret = int(inpart.params['return'])
1616 ret = int(inpart.params['return'])
1601 partid = int(inpart.params['in-reply-to'])
1617 partid = int(inpart.params['in-reply-to'])
1602 op.records.add('pushkey', {'return': ret}, partid)
1618 op.records.add('pushkey', {'return': ret}, partid)
1603
1619
1604 @parthandler('obsmarkers')
1620 @parthandler('obsmarkers')
1605 def handleobsmarker(op, inpart):
1621 def handleobsmarker(op, inpart):
1606 """add a stream of obsmarkers to the repo"""
1622 """add a stream of obsmarkers to the repo"""
1607 tr = op.gettransaction()
1623 tr = op.gettransaction()
1608 markerdata = inpart.read()
1624 markerdata = inpart.read()
1609 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1625 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1610 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1626 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1611 % len(markerdata))
1627 % len(markerdata))
1612 # The mergemarkers call will crash if marker creation is not enabled.
1628 # The mergemarkers call will crash if marker creation is not enabled.
1613 # we want to avoid this if the part is advisory.
1629 # we want to avoid this if the part is advisory.
1614 if not inpart.mandatory and op.repo.obsstore.readonly:
1630 if not inpart.mandatory and op.repo.obsstore.readonly:
1615 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled')
1631 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled')
1616 return
1632 return
1617 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1633 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1618 if new:
1634 if new:
1619 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1635 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1620 op.records.add('obsmarkers', {'new': new})
1636 op.records.add('obsmarkers', {'new': new})
1621 if op.reply is not None:
1637 if op.reply is not None:
1622 rpart = op.reply.newpart('reply:obsmarkers')
1638 rpart = op.reply.newpart('reply:obsmarkers')
1623 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1639 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1624 rpart.addparam('new', '%i' % new, mandatory=False)
1640 rpart.addparam('new', '%i' % new, mandatory=False)
1625
1641
1626
1642
1627 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1643 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1628 def handleobsmarkerreply(op, inpart):
1644 def handleobsmarkerreply(op, inpart):
1629 """retrieve the result of a pushkey request"""
1645 """retrieve the result of a pushkey request"""
1630 ret = int(inpart.params['new'])
1646 ret = int(inpart.params['new'])
1631 partid = int(inpart.params['in-reply-to'])
1647 partid = int(inpart.params['in-reply-to'])
1632 op.records.add('obsmarkers', {'new': ret}, partid)
1648 op.records.add('obsmarkers', {'new': ret}, partid)
1633
1649
1634 @parthandler('hgtagsfnodes')
1650 @parthandler('hgtagsfnodes')
1635 def handlehgtagsfnodes(op, inpart):
1651 def handlehgtagsfnodes(op, inpart):
1636 """Applies .hgtags fnodes cache entries to the local repo.
1652 """Applies .hgtags fnodes cache entries to the local repo.
1637
1653
1638 Payload is pairs of 20 byte changeset nodes and filenodes.
1654 Payload is pairs of 20 byte changeset nodes and filenodes.
1639 """
1655 """
1640 # Grab the transaction so we ensure that we have the lock at this point.
1656 # Grab the transaction so we ensure that we have the lock at this point.
1641 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1657 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1642 op.gettransaction()
1658 op.gettransaction()
1643 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
1659 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
1644
1660
1645 count = 0
1661 count = 0
1646 while True:
1662 while True:
1647 node = inpart.read(20)
1663 node = inpart.read(20)
1648 fnode = inpart.read(20)
1664 fnode = inpart.read(20)
1649 if len(node) < 20 or len(fnode) < 20:
1665 if len(node) < 20 or len(fnode) < 20:
1650 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
1666 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
1651 break
1667 break
1652 cache.setfnode(node, fnode)
1668 cache.setfnode(node, fnode)
1653 count += 1
1669 count += 1
1654
1670
1655 cache.write()
1671 cache.write()
1656 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
1672 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
@@ -1,893 +1,896 b''
1 #require killdaemons serve zstd
1 #require killdaemons serve zstd
2
2
3 Client version is embedded in HTTP request and is effectively dynamic. Pin the
3 Client version is embedded in HTTP request and is effectively dynamic. Pin the
4 version so behavior is deterministic.
4 version so behavior is deterministic.
5
5
6 $ cat > fakeversion.py << EOF
6 $ cat > fakeversion.py << EOF
7 > from mercurial import util
7 > from mercurial import util
8 > util.version = lambda: '4.2'
8 > util.version = lambda: '4.2'
9 > EOF
9 > EOF
10
10
11 $ cat >> $HGRCPATH << EOF
11 $ cat >> $HGRCPATH << EOF
12 > [extensions]
12 > [extensions]
13 > fakeversion = `pwd`/fakeversion.py
13 > fakeversion = `pwd`/fakeversion.py
14 > EOF
14 > EOF
15
15
16 $ hg init server0
16 $ hg init server0
17 $ cd server0
17 $ cd server0
18 $ touch foo
18 $ touch foo
19 $ hg -q commit -A -m initial
19 $ hg -q commit -A -m initial
20
20
21 Also disable compression because zstd is optional and causes output to vary
21 Also disable compression because zstd is optional and causes output to vary
22 and because debugging partial responses is hard when compression is involved
22 and because debugging partial responses is hard when compression is involved
23
23
24 $ cat > .hg/hgrc << EOF
24 $ cat > .hg/hgrc << EOF
25 > [extensions]
25 > [extensions]
26 > badserver = $TESTDIR/badserverext.py
26 > badserver = $TESTDIR/badserverext.py
27 > [server]
27 > [server]
28 > compressionengines = none
28 > compressionengines = none
29 > EOF
29 > EOF
30
30
31 Failure to accept() socket should result in connection related error message
31 Failure to accept() socket should result in connection related error message
32
32
33 $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
33 $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
34 $ cat hg.pid > $DAEMON_PIDS
34 $ cat hg.pid > $DAEMON_PIDS
35
35
36 $ hg clone http://localhost:$HGPORT/ clone
36 $ hg clone http://localhost:$HGPORT/ clone
37 abort: error: Connection reset by peer (no-windows !)
37 abort: error: Connection reset by peer (no-windows !)
38 abort: error: An existing connection was forcibly closed by the remote host (windows !)
38 abort: error: An existing connection was forcibly closed by the remote host (windows !)
39 [255]
39 [255]
40
40
41 (The server exits on its own, but there is a race between that and starting a new server.
41 (The server exits on its own, but there is a race between that and starting a new server.
42 So ensure the process is dead.)
42 So ensure the process is dead.)
43
43
44 $ killdaemons.py $DAEMON_PIDS
44 $ killdaemons.py $DAEMON_PIDS
45
45
46 Failure immediately after accept() should yield connection related error message
46 Failure immediately after accept() should yield connection related error message
47
47
48 $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
48 $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
49 $ cat hg.pid > $DAEMON_PIDS
49 $ cat hg.pid > $DAEMON_PIDS
50
50
51 $ hg clone http://localhost:$HGPORT/ clone
51 $ hg clone http://localhost:$HGPORT/ clone
52 abort: error: Connection reset by peer (no-windows !)
52 abort: error: Connection reset by peer (no-windows !)
53 abort: error: An existing connection was forcibly closed by the remote host (windows !)
53 abort: error: An existing connection was forcibly closed by the remote host (windows !)
54 [255]
54 [255]
55
55
56 $ killdaemons.py $DAEMON_PIDS
56 $ killdaemons.py $DAEMON_PIDS
57
57
58 Failure to read all bytes in initial HTTP request should yield connection related error message
58 Failure to read all bytes in initial HTTP request should yield connection related error message
59
59
60 $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
60 $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
61 $ cat hg.pid > $DAEMON_PIDS
61 $ cat hg.pid > $DAEMON_PIDS
62
62
63 TODO this error message is not very good
63 TODO this error message is not very good
64
64
65 $ hg clone http://localhost:$HGPORT/ clone
65 $ hg clone http://localhost:$HGPORT/ clone
66 abort: error: ''
66 abort: error: ''
67 [255]
67 [255]
68
68
69 $ killdaemons.py $DAEMON_PIDS
69 $ killdaemons.py $DAEMON_PIDS
70
70
71 $ cat error.log
71 $ cat error.log
72 readline(1 from 65537) -> (1) G
72 readline(1 from 65537) -> (1) G
73 read limit reached; closing socket
73 read limit reached; closing socket
74
74
75 $ rm -f error.log
75 $ rm -f error.log
76
76
77 Same failure, but server reads full HTTP request line
77 Same failure, but server reads full HTTP request line
78
78
79 $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
79 $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
80 $ cat hg.pid > $DAEMON_PIDS
80 $ cat hg.pid > $DAEMON_PIDS
81 $ hg clone http://localhost:$HGPORT/ clone
81 $ hg clone http://localhost:$HGPORT/ clone
82 abort: error: ''
82 abort: error: ''
83 [255]
83 [255]
84
84
85 $ killdaemons.py $DAEMON_PIDS
85 $ killdaemons.py $DAEMON_PIDS
86
86
87 $ cat error.log
87 $ cat error.log
88 readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
88 readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
89 readline(7 from -1) -> (7) Accept-
89 readline(7 from -1) -> (7) Accept-
90 read limit reached; closing socket
90 read limit reached; closing socket
91
91
92 $ rm -f error.log
92 $ rm -f error.log
93
93
94 Failure on subsequent HTTP request on the same socket (cmd?batch)
94 Failure on subsequent HTTP request on the same socket (cmd?batch)
95
95
96 $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
96 $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
97 $ cat hg.pid > $DAEMON_PIDS
97 $ cat hg.pid > $DAEMON_PIDS
98 $ hg clone http://localhost:$HGPORT/ clone
98 $ hg clone http://localhost:$HGPORT/ clone
99 abort: error: ''
99 abort: error: ''
100 [255]
100 [255]
101
101
102 $ killdaemons.py $DAEMON_PIDS
102 $ killdaemons.py $DAEMON_PIDS
103
103
104 $ cat error.log
104 $ cat error.log
105 readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
105 readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
106 readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
106 readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
107 readline(150 from -1) -> (35) accept: application/mercurial-0.1\r\n
107 readline(150 from -1) -> (35) accept: application/mercurial-0.1\r\n
108 readline(115 from -1) -> (23) host: localhost:$HGPORT\r\n
108 readline(115 from -1) -> (23) host: localhost:$HGPORT\r\n
109 readline(92 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
109 readline(92 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
110 readline(43 from -1) -> (2) \r\n
110 readline(43 from -1) -> (2) \r\n
111 write(36) -> HTTP/1.1 200 Script output follows\r\n
111 write(36) -> HTTP/1.1 200 Script output follows\r\n
112 write(23) -> Server: badhttpserver\r\n
112 write(23) -> Server: badhttpserver\r\n
113 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
113 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
114 write(41) -> Content-Type: application/mercurial-0.1\r\n
114 write(41) -> Content-Type: application/mercurial-0.1\r\n
115 write(21) -> Content-Length: 405\r\n
115 write(21) -> Content-Length: 405\r\n
116 write(2) -> \r\n
116 write(2) -> \r\n
117 write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
117 write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
118 readline(41 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
118 readline(41 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
119 readline(15 from -1) -> (15) Accept-Encoding
119 readline(15 from -1) -> (15) Accept-Encoding
120 read limit reached; closing socket
120 read limit reached; closing socket
121 readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
121 readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
122 readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
122 readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
123 readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
123 readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
124 readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
124 readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
125 readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
125 readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
126 readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
126 readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
127 readline(4 from -1) -> (4) host
127 readline(4 from -1) -> (4) host
128 read limit reached; closing socket
128 read limit reached; closing socket
129
129
130 $ rm -f error.log
130 $ rm -f error.log
131
131
132 Failure to read getbundle HTTP request
132 Failure to read getbundle HTTP request
133
133
134 $ hg --config badserver.closeafterrecvbytes=292 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
134 $ hg --config badserver.closeafterrecvbytes=292 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
135 $ cat hg.pid > $DAEMON_PIDS
135 $ cat hg.pid > $DAEMON_PIDS
136 $ hg clone http://localhost:$HGPORT/ clone
136 $ hg clone http://localhost:$HGPORT/ clone
137 requesting all changes
137 requesting all changes
138 abort: error: ''
138 abort: error: ''
139 [255]
139 [255]
140
140
141 $ killdaemons.py $DAEMON_PIDS
141 $ killdaemons.py $DAEMON_PIDS
142
142
143 $ cat error.log
143 $ cat error.log
144 readline(292 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
144 readline(292 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
145 readline(259 from -1) -> (27) Accept-Encoding: identity\r\n
145 readline(259 from -1) -> (27) Accept-Encoding: identity\r\n
146 readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
146 readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
147 readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
147 readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
148 readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
148 readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
149 readline(125 from -1) -> (2) \r\n
149 readline(125 from -1) -> (2) \r\n
150 write(36) -> HTTP/1.1 200 Script output follows\r\n
150 write(36) -> HTTP/1.1 200 Script output follows\r\n
151 write(23) -> Server: badhttpserver\r\n
151 write(23) -> Server: badhttpserver\r\n
152 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
152 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
153 write(41) -> Content-Type: application/mercurial-0.1\r\n
153 write(41) -> Content-Type: application/mercurial-0.1\r\n
154 write(21) -> Content-Length: 405\r\n
154 write(21) -> Content-Length: 405\r\n
155 write(2) -> \r\n
155 write(2) -> \r\n
156 write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
156 write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
157 readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
157 readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
158 readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
158 readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
159 readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
159 readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
160 readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
160 readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
161 read limit reached; closing socket
161 read limit reached; closing socket
162 readline(292 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
162 readline(292 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
163 readline(266 from -1) -> (27) Accept-Encoding: identity\r\n
163 readline(266 from -1) -> (27) Accept-Encoding: identity\r\n
164 readline(239 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
164 readline(239 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
165 readline(210 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
165 readline(210 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
166 readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
166 readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
167 readline(121 from -1) -> (35) accept: application/mercurial-0.1\r\n
167 readline(121 from -1) -> (35) accept: application/mercurial-0.1\r\n
168 readline(86 from -1) -> (23) host: localhost:$HGPORT\r\n
168 readline(86 from -1) -> (23) host: localhost:$HGPORT\r\n
169 readline(63 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
169 readline(63 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
170 readline(14 from -1) -> (2) \r\n
170 readline(14 from -1) -> (2) \r\n
171 write(36) -> HTTP/1.1 200 Script output follows\r\n
171 write(36) -> HTTP/1.1 200 Script output follows\r\n
172 write(23) -> Server: badhttpserver\r\n
172 write(23) -> Server: badhttpserver\r\n
173 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
173 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
174 write(41) -> Content-Type: application/mercurial-0.1\r\n
174 write(41) -> Content-Type: application/mercurial-0.1\r\n
175 write(20) -> Content-Length: 42\r\n
175 write(20) -> Content-Length: 42\r\n
176 write(2) -> \r\n
176 write(2) -> \r\n
177 write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
177 write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
178 readline(12 from 65537) -> (12) GET /?cmd=ge
178 readline(12 from 65537) -> (12) GET /?cmd=ge
179 read limit reached; closing socket
179 read limit reached; closing socket
180 readline(292 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
180 readline(292 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
181 readline(262 from -1) -> (27) Accept-Encoding: identity\r\n
181 readline(262 from -1) -> (27) Accept-Encoding: identity\r\n
182 readline(235 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
182 readline(235 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
183 readline(206 from -1) -> (206) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Ali
183 readline(206 from -1) -> (206) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Ali
184 read limit reached; closing socket
184 read limit reached; closing socket
185
185
186 $ rm -f error.log
186 $ rm -f error.log
187
187
188 Now do a variation using POST to send arguments
188 Now do a variation using POST to send arguments
189
189
190 $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
190 $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
191 $ cat hg.pid > $DAEMON_PIDS
191 $ cat hg.pid > $DAEMON_PIDS
192
192
193 $ hg clone http://localhost:$HGPORT/ clone
193 $ hg clone http://localhost:$HGPORT/ clone
194 abort: error: ''
194 abort: error: ''
195 [255]
195 [255]
196
196
197 $ killdaemons.py $DAEMON_PIDS
197 $ killdaemons.py $DAEMON_PIDS
198
198
199 $ cat error.log
199 $ cat error.log
200 readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
200 readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
201 readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
201 readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
202 readline(255 from -1) -> (35) accept: application/mercurial-0.1\r\n
202 readline(255 from -1) -> (35) accept: application/mercurial-0.1\r\n
203 readline(220 from -1) -> (23) host: localhost:$HGPORT\r\n
203 readline(220 from -1) -> (23) host: localhost:$HGPORT\r\n
204 readline(197 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
204 readline(197 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
205 readline(148 from -1) -> (2) \r\n
205 readline(148 from -1) -> (2) \r\n
206 write(36) -> HTTP/1.1 200 Script output follows\r\n
206 write(36) -> HTTP/1.1 200 Script output follows\r\n
207 write(23) -> Server: badhttpserver\r\n
207 write(23) -> Server: badhttpserver\r\n
208 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
208 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
209 write(41) -> Content-Type: application/mercurial-0.1\r\n
209 write(41) -> Content-Type: application/mercurial-0.1\r\n
210 write(21) -> Content-Length: 418\r\n
210 write(21) -> Content-Length: 418\r\n
211 write(2) -> \r\n
211 write(2) -> \r\n
212 write(418) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs httpmediatype=0.1rx,0.1tx,0.2tx compression=none
212 write(418) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs httpmediatype=0.1rx,0.1tx,0.2tx compression=none
213 readline(146 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
213 readline(146 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
214 readline(119 from -1) -> (27) Accept-Encoding: identity\r\n
214 readline(119 from -1) -> (27) Accept-Encoding: identity\r\n
215 readline(92 from -1) -> (41) content-type: application/mercurial-0.1\r\n
215 readline(92 from -1) -> (41) content-type: application/mercurial-0.1\r\n
216 readline(51 from -1) -> (19) vary: X-HgProto-1\r\n
216 readline(51 from -1) -> (19) vary: X-HgProto-1\r\n
217 readline(32 from -1) -> (19) x-hgargs-post: 28\r\n
217 readline(32 from -1) -> (19) x-hgargs-post: 28\r\n
218 readline(13 from -1) -> (13) x-hgproto-1:
218 readline(13 from -1) -> (13) x-hgproto-1:
219 read limit reached; closing socket
219 read limit reached; closing socket
220 readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
220 readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
221 readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
221 readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
222 readline(261 from -1) -> (41) content-type: application/mercurial-0.1\r\n
222 readline(261 from -1) -> (41) content-type: application/mercurial-0.1\r\n
223 readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
223 readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
224 readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
224 readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
225 readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
225 readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
226 readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
226 readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
227 readline(99 from -1) -> (20) content-length: 28\r\n
227 readline(99 from -1) -> (20) content-length: 28\r\n
228 readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
228 readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
229 readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
229 readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
230 readline(7 from -1) -> (2) \r\n
230 readline(7 from -1) -> (2) \r\n
231 read(5 from 28) -> (5) cmds=
231 read(5 from 28) -> (5) cmds=
232 read limit reached, closing socket
232 read limit reached, closing socket
233 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
233 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
234
234
235 $ rm -f error.log
235 $ rm -f error.log
236
236
237 Now move on to partial server responses
237 Now move on to partial server responses
238
238
239 Server sends a single character from the HTTP response line
239 Server sends a single character from the HTTP response line
240
240
241 $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
241 $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
242 $ cat hg.pid > $DAEMON_PIDS
242 $ cat hg.pid > $DAEMON_PIDS
243
243
244 $ hg clone http://localhost:$HGPORT/ clone
244 $ hg clone http://localhost:$HGPORT/ clone
245 abort: error: H
245 abort: error: H
246 [255]
246 [255]
247
247
248 $ killdaemons.py $DAEMON_PIDS
248 $ killdaemons.py $DAEMON_PIDS
249
249
250 $ cat error.log
250 $ cat error.log
251 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
251 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
252 readline(-1) -> (27) Accept-Encoding: identity\r\n
252 readline(-1) -> (27) Accept-Encoding: identity\r\n
253 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
253 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
254 readline(-1) -> (23) host: localhost:$HGPORT\r\n
254 readline(-1) -> (23) host: localhost:$HGPORT\r\n
255 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
255 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
256 readline(-1) -> (2) \r\n
256 readline(-1) -> (2) \r\n
257 write(1 from 36) -> (0) H
257 write(1 from 36) -> (0) H
258 write limit reached; closing socket
258 write limit reached; closing socket
259 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
259 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
260
260
261 $ rm -f error.log
261 $ rm -f error.log
262
262
263 Server sends an incomplete capabilities response body
263 Server sends an incomplete capabilities response body
264
264
265 $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
265 $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
266 $ cat hg.pid > $DAEMON_PIDS
266 $ cat hg.pid > $DAEMON_PIDS
267
267
268 $ hg clone http://localhost:$HGPORT/ clone
268 $ hg clone http://localhost:$HGPORT/ clone
269 abort: HTTP request error (incomplete response; expected 385 bytes got 20)
269 abort: HTTP request error (incomplete response; expected 385 bytes got 20)
270 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
270 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
271 [255]
271 [255]
272
272
273 $ killdaemons.py $DAEMON_PIDS
273 $ killdaemons.py $DAEMON_PIDS
274
274
275 $ cat error.log
275 $ cat error.log
276 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
276 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
277 readline(-1) -> (27) Accept-Encoding: identity\r\n
277 readline(-1) -> (27) Accept-Encoding: identity\r\n
278 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
278 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
279 readline(-1) -> (23) host: localhost:$HGPORT\r\n
279 readline(-1) -> (23) host: localhost:$HGPORT\r\n
280 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
280 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
281 readline(-1) -> (2) \r\n
281 readline(-1) -> (2) \r\n
282 write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
282 write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
283 write(23 from 23) -> (121) Server: badhttpserver\r\n
283 write(23 from 23) -> (121) Server: badhttpserver\r\n
284 write(37 from 37) -> (84) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
284 write(37 from 37) -> (84) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
285 write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
285 write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
286 write(21 from 21) -> (22) Content-Length: 405\r\n
286 write(21 from 21) -> (22) Content-Length: 405\r\n
287 write(2 from 2) -> (20) \r\n
287 write(2 from 2) -> (20) \r\n
288 write(20 from 405) -> (0) lookup changegroupsu
288 write(20 from 405) -> (0) lookup changegroupsu
289 write limit reached; closing socket
289 write limit reached; closing socket
290
290
291 $ rm -f error.log
291 $ rm -f error.log
292
292
293 Server sends incomplete headers for batch request
293 Server sends incomplete headers for batch request
294
294
295 $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
295 $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
296 $ cat hg.pid > $DAEMON_PIDS
296 $ cat hg.pid > $DAEMON_PIDS
297
297
298 TODO this output is horrible
298 TODO this output is horrible
299
299
300 $ hg clone http://localhost:$HGPORT/ clone
300 $ hg clone http://localhost:$HGPORT/ clone
301 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
301 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
302 ---%<--- (application/mercuria)
302 ---%<--- (application/mercuria)
303
303
304 ---%<---
304 ---%<---
305 !
305 !
306 [255]
306 [255]
307
307
308 $ killdaemons.py $DAEMON_PIDS
308 $ killdaemons.py $DAEMON_PIDS
309
309
310 $ cat error.log
310 $ cat error.log
311 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
311 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
312 readline(-1) -> (27) Accept-Encoding: identity\r\n
312 readline(-1) -> (27) Accept-Encoding: identity\r\n
313 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
313 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
314 readline(-1) -> (23) host: localhost:$HGPORT\r\n
314 readline(-1) -> (23) host: localhost:$HGPORT\r\n
315 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
315 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
316 readline(-1) -> (2) \r\n
316 readline(-1) -> (2) \r\n
317 write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
317 write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
318 write(23 from 23) -> (636) Server: badhttpserver\r\n
318 write(23 from 23) -> (636) Server: badhttpserver\r\n
319 write(37 from 37) -> (599) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
319 write(37 from 37) -> (599) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
320 write(41 from 41) -> (558) Content-Type: application/mercurial-0.1\r\n
320 write(41 from 41) -> (558) Content-Type: application/mercurial-0.1\r\n
321 write(21 from 21) -> (537) Content-Length: 405\r\n
321 write(21 from 21) -> (537) Content-Length: 405\r\n
322 write(2 from 2) -> (535) \r\n
322 write(2 from 2) -> (535) \r\n
323 write(405 from 405) -> (130) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
323 write(405 from 405) -> (130) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
324 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
324 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
325 readline(-1) -> (27) Accept-Encoding: identity\r\n
325 readline(-1) -> (27) Accept-Encoding: identity\r\n
326 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
326 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
327 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
327 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
328 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
328 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
329 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
329 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
330 readline(-1) -> (23) host: localhost:$HGPORT\r\n
330 readline(-1) -> (23) host: localhost:$HGPORT\r\n
331 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
331 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
332 readline(-1) -> (2) \r\n
332 readline(-1) -> (2) \r\n
333 write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
333 write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
334 write(23 from 23) -> (71) Server: badhttpserver\r\n
334 write(23 from 23) -> (71) Server: badhttpserver\r\n
335 write(37 from 37) -> (34) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
335 write(37 from 37) -> (34) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
336 write(34 from 41) -> (0) Content-Type: application/mercuria
336 write(34 from 41) -> (0) Content-Type: application/mercuria
337 write limit reached; closing socket
337 write limit reached; closing socket
338 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
338 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
339
339
340 $ rm -f error.log
340 $ rm -f error.log
341
341
342 Server sends an incomplete HTTP response body to batch request
342 Server sends an incomplete HTTP response body to batch request
343
343
344 $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
344 $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
345 $ cat hg.pid > $DAEMON_PIDS
345 $ cat hg.pid > $DAEMON_PIDS
346
346
347 TODO client spews a stack due to uncaught ValueError in batch.results()
347 TODO client spews a stack due to uncaught ValueError in batch.results()
348 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
348 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
349 [1]
349 [1]
350
350
351 $ killdaemons.py $DAEMON_PIDS
351 $ killdaemons.py $DAEMON_PIDS
352
352
353 $ cat error.log
353 $ cat error.log
354 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
354 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
355 readline(-1) -> (27) Accept-Encoding: identity\r\n
355 readline(-1) -> (27) Accept-Encoding: identity\r\n
356 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
356 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
357 readline(-1) -> (23) host: localhost:$HGPORT\r\n
357 readline(-1) -> (23) host: localhost:$HGPORT\r\n
358 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
358 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
359 readline(-1) -> (2) \r\n
359 readline(-1) -> (2) \r\n
360 write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
360 write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
361 write(23 from 23) -> (701) Server: badhttpserver\r\n
361 write(23 from 23) -> (701) Server: badhttpserver\r\n
362 write(37 from 37) -> (664) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
362 write(37 from 37) -> (664) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
363 write(41 from 41) -> (623) Content-Type: application/mercurial-0.1\r\n
363 write(41 from 41) -> (623) Content-Type: application/mercurial-0.1\r\n
364 write(21 from 21) -> (602) Content-Length: 405\r\n
364 write(21 from 21) -> (602) Content-Length: 405\r\n
365 write(2 from 2) -> (600) \r\n
365 write(2 from 2) -> (600) \r\n
366 write(405 from 405) -> (195) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
366 write(405 from 405) -> (195) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
367 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
367 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
368 readline(-1) -> (27) Accept-Encoding: identity\r\n
368 readline(-1) -> (27) Accept-Encoding: identity\r\n
369 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
369 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
370 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
370 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
371 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
371 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
372 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
372 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
373 readline(-1) -> (23) host: localhost:$HGPORT\r\n
373 readline(-1) -> (23) host: localhost:$HGPORT\r\n
374 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
374 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
375 readline(-1) -> (2) \r\n
375 readline(-1) -> (2) \r\n
376 write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
376 write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
377 write(23 from 23) -> (136) Server: badhttpserver\r\n
377 write(23 from 23) -> (136) Server: badhttpserver\r\n
378 write(37 from 37) -> (99) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
378 write(37 from 37) -> (99) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
379 write(41 from 41) -> (58) Content-Type: application/mercurial-0.1\r\n
379 write(41 from 41) -> (58) Content-Type: application/mercurial-0.1\r\n
380 write(20 from 20) -> (38) Content-Length: 42\r\n
380 write(20 from 20) -> (38) Content-Length: 42\r\n
381 write(2 from 2) -> (36) \r\n
381 write(2 from 2) -> (36) \r\n
382 write(36 from 42) -> (0) 96ee1d7354c4ad7372047672c36a1f561e3a
382 write(36 from 42) -> (0) 96ee1d7354c4ad7372047672c36a1f561e3a
383 write limit reached; closing socket
383 write limit reached; closing socket
384
384
385 $ rm -f error.log
385 $ rm -f error.log
386
386
387 Server sends incomplete headers for getbundle response
387 Server sends incomplete headers for getbundle response
388
388
389 $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
389 $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
390 $ cat hg.pid > $DAEMON_PIDS
390 $ cat hg.pid > $DAEMON_PIDS
391
391
392 TODO this output is terrible
392 TODO this output is terrible
393
393
394 $ hg clone http://localhost:$HGPORT/ clone
394 $ hg clone http://localhost:$HGPORT/ clone
395 requesting all changes
395 requesting all changes
396 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
396 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
397 ---%<--- (application/mercuri)
397 ---%<--- (application/mercuri)
398
398
399 ---%<---
399 ---%<---
400 !
400 !
401 [255]
401 [255]
402
402
403 $ killdaemons.py $DAEMON_PIDS
403 $ killdaemons.py $DAEMON_PIDS
404
404
405 $ cat error.log
405 $ cat error.log
406 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
406 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
407 readline(-1) -> (27) Accept-Encoding: identity\r\n
407 readline(-1) -> (27) Accept-Encoding: identity\r\n
408 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
408 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
409 readline(-1) -> (23) host: localhost:$HGPORT\r\n
409 readline(-1) -> (23) host: localhost:$HGPORT\r\n
410 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
410 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
411 readline(-1) -> (2) \r\n
411 readline(-1) -> (2) \r\n
412 write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
412 write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
413 write(23 from 23) -> (836) Server: badhttpserver\r\n
413 write(23 from 23) -> (836) Server: badhttpserver\r\n
414 write(37 from 37) -> (799) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
414 write(37 from 37) -> (799) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
415 write(41 from 41) -> (758) Content-Type: application/mercurial-0.1\r\n
415 write(41 from 41) -> (758) Content-Type: application/mercurial-0.1\r\n
416 write(21 from 21) -> (737) Content-Length: 405\r\n
416 write(21 from 21) -> (737) Content-Length: 405\r\n
417 write(2 from 2) -> (735) \r\n
417 write(2 from 2) -> (735) \r\n
418 write(405 from 405) -> (330) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
418 write(405 from 405) -> (330) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
419 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
419 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
420 readline(-1) -> (27) Accept-Encoding: identity\r\n
420 readline(-1) -> (27) Accept-Encoding: identity\r\n
421 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
421 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
422 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
422 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
423 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
423 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
424 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
424 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
425 readline(-1) -> (23) host: localhost:$HGPORT\r\n
425 readline(-1) -> (23) host: localhost:$HGPORT\r\n
426 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
426 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
427 readline(-1) -> (2) \r\n
427 readline(-1) -> (2) \r\n
428 write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
428 write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
429 write(23 from 23) -> (271) Server: badhttpserver\r\n
429 write(23 from 23) -> (271) Server: badhttpserver\r\n
430 write(37 from 37) -> (234) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
430 write(37 from 37) -> (234) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
431 write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n
431 write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n
432 write(20 from 20) -> (173) Content-Length: 42\r\n
432 write(20 from 20) -> (173) Content-Length: 42\r\n
433 write(2 from 2) -> (171) \r\n
433 write(2 from 2) -> (171) \r\n
434 write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
434 write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
435 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
435 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
436 readline(-1) -> (27) Accept-Encoding: identity\r\n
436 readline(-1) -> (27) Accept-Encoding: identity\r\n
437 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
437 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
438 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
438 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
439 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
439 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
440 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
440 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
441 readline(-1) -> (23) host: localhost:$HGPORT\r\n
441 readline(-1) -> (23) host: localhost:$HGPORT\r\n
442 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
442 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
443 readline(-1) -> (2) \r\n
443 readline(-1) -> (2) \r\n
444 write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
444 write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
445 write(23 from 23) -> (70) Server: badhttpserver\r\n
445 write(23 from 23) -> (70) Server: badhttpserver\r\n
446 write(37 from 37) -> (33) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
446 write(37 from 37) -> (33) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
447 write(33 from 41) -> (0) Content-Type: application/mercuri
447 write(33 from 41) -> (0) Content-Type: application/mercuri
448 write limit reached; closing socket
448 write limit reached; closing socket
449 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
449 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
450
450
451 $ rm -f error.log
451 $ rm -f error.log
452
452
453 Server sends empty HTTP body for getbundle
453 Server sends empty HTTP body for getbundle
454
454
455 $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
455 $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
456 $ cat hg.pid > $DAEMON_PIDS
456 $ cat hg.pid > $DAEMON_PIDS
457
457
458 $ hg clone http://localhost:$HGPORT/ clone
458 $ hg clone http://localhost:$HGPORT/ clone
459 requesting all changes
459 requesting all changes
460 abort: HTTP request error (incomplete response)
460 abort: HTTP request error (incomplete response)
461 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
461 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
462 [255]
462 [255]
463
463
464 $ killdaemons.py $DAEMON_PIDS
464 $ killdaemons.py $DAEMON_PIDS
465
465
466 $ cat error.log
466 $ cat error.log
467 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
467 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
468 readline(-1) -> (27) Accept-Encoding: identity\r\n
468 readline(-1) -> (27) Accept-Encoding: identity\r\n
469 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
469 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
470 readline(-1) -> (23) host: localhost:$HGPORT\r\n
470 readline(-1) -> (23) host: localhost:$HGPORT\r\n
471 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
471 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
472 readline(-1) -> (2) \r\n
472 readline(-1) -> (2) \r\n
473 write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
473 write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
474 write(23 from 23) -> (874) Server: badhttpserver\r\n
474 write(23 from 23) -> (874) Server: badhttpserver\r\n
475 write(37 from 37) -> (837) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
475 write(37 from 37) -> (837) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
476 write(41 from 41) -> (796) Content-Type: application/mercurial-0.1\r\n
476 write(41 from 41) -> (796) Content-Type: application/mercurial-0.1\r\n
477 write(21 from 21) -> (775) Content-Length: 405\r\n
477 write(21 from 21) -> (775) Content-Length: 405\r\n
478 write(2 from 2) -> (773) \r\n
478 write(2 from 2) -> (773) \r\n
479 write(405 from 405) -> (368) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
479 write(405 from 405) -> (368) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
480 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
480 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
481 readline(-1) -> (27) Accept-Encoding: identity\r\n
481 readline(-1) -> (27) Accept-Encoding: identity\r\n
482 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
482 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
483 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
483 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
484 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
484 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
485 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
485 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
486 readline(-1) -> (23) host: localhost:$HGPORT\r\n
486 readline(-1) -> (23) host: localhost:$HGPORT\r\n
487 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
487 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
488 readline(-1) -> (2) \r\n
488 readline(-1) -> (2) \r\n
489 write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
489 write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
490 write(23 from 23) -> (309) Server: badhttpserver\r\n
490 write(23 from 23) -> (309) Server: badhttpserver\r\n
491 write(37 from 37) -> (272) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
491 write(37 from 37) -> (272) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
492 write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n
492 write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n
493 write(20 from 20) -> (211) Content-Length: 42\r\n
493 write(20 from 20) -> (211) Content-Length: 42\r\n
494 write(2 from 2) -> (209) \r\n
494 write(2 from 2) -> (209) \r\n
495 write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
495 write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
496 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
496 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
497 readline(-1) -> (27) Accept-Encoding: identity\r\n
497 readline(-1) -> (27) Accept-Encoding: identity\r\n
498 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
498 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
499 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
499 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
500 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
500 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
501 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
501 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
502 readline(-1) -> (23) host: localhost:$HGPORT\r\n
502 readline(-1) -> (23) host: localhost:$HGPORT\r\n
503 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
503 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
504 readline(-1) -> (2) \r\n
504 readline(-1) -> (2) \r\n
505 write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
505 write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
506 write(23 from 23) -> (108) Server: badhttpserver\r\n
506 write(23 from 23) -> (108) Server: badhttpserver\r\n
507 write(37 from 37) -> (71) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
507 write(37 from 37) -> (71) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
508 write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n
508 write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n
509 write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n
509 write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n
510 write(2 from 2) -> (0) \r\n
510 write(2 from 2) -> (0) \r\n
511 write limit reached; closing socket
511 write limit reached; closing socket
512 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
512 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
513
513
514 $ rm -f error.log
514 $ rm -f error.log
515
515
516 Server sends partial compression string
516 Server sends partial compression string
517
517
518 $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
518 $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
519 $ cat hg.pid > $DAEMON_PIDS
519 $ cat hg.pid > $DAEMON_PIDS
520
520
521 $ hg clone http://localhost:$HGPORT/ clone
521 $ hg clone http://localhost:$HGPORT/ clone
522 requesting all changes
522 requesting all changes
523 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
523 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
524 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
524 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
525 [255]
525 [255]
526
526
527 $ killdaemons.py $DAEMON_PIDS
527 $ killdaemons.py $DAEMON_PIDS
528
528
529 $ cat error.log
529 $ cat error.log
530 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
530 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
531 readline(-1) -> (27) Accept-Encoding: identity\r\n
531 readline(-1) -> (27) Accept-Encoding: identity\r\n
532 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
532 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
533 readline(-1) -> (23) host: localhost:$HGPORT\r\n
533 readline(-1) -> (23) host: localhost:$HGPORT\r\n
534 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
534 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
535 readline(-1) -> (2) \r\n
535 readline(-1) -> (2) \r\n
536 write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
536 write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
537 write(23 from 23) -> (886) Server: badhttpserver\r\n
537 write(23 from 23) -> (886) Server: badhttpserver\r\n
538 write(37 from 37) -> (849) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
538 write(37 from 37) -> (849) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
539 write(41 from 41) -> (808) Content-Type: application/mercurial-0.1\r\n
539 write(41 from 41) -> (808) Content-Type: application/mercurial-0.1\r\n
540 write(21 from 21) -> (787) Content-Length: 405\r\n
540 write(21 from 21) -> (787) Content-Length: 405\r\n
541 write(2 from 2) -> (785) \r\n
541 write(2 from 2) -> (785) \r\n
542 write(405 from 405) -> (380) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
542 write(405 from 405) -> (380) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
543 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
543 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
544 readline(-1) -> (27) Accept-Encoding: identity\r\n
544 readline(-1) -> (27) Accept-Encoding: identity\r\n
545 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
545 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
546 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
546 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
547 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
547 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
548 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
548 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
549 readline(-1) -> (23) host: localhost:$HGPORT\r\n
549 readline(-1) -> (23) host: localhost:$HGPORT\r\n
550 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
550 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
551 readline(-1) -> (2) \r\n
551 readline(-1) -> (2) \r\n
552 write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
552 write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
553 write(23 from 23) -> (321) Server: badhttpserver\r\n
553 write(23 from 23) -> (321) Server: badhttpserver\r\n
554 write(37 from 37) -> (284) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
554 write(37 from 37) -> (284) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
555 write(41 from 41) -> (243) Content-Type: application/mercurial-0.1\r\n
555 write(41 from 41) -> (243) Content-Type: application/mercurial-0.1\r\n
556 write(20 from 20) -> (223) Content-Length: 42\r\n
556 write(20 from 20) -> (223) Content-Length: 42\r\n
557 write(2 from 2) -> (221) \r\n
557 write(2 from 2) -> (221) \r\n
558 write(42 from 42) -> (179) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
558 write(42 from 42) -> (179) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
559 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
559 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
560 readline(-1) -> (27) Accept-Encoding: identity\r\n
560 readline(-1) -> (27) Accept-Encoding: identity\r\n
561 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
561 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
562 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
562 readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
563 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
563 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
564 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
564 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
565 readline(-1) -> (23) host: localhost:$HGPORT\r\n
565 readline(-1) -> (23) host: localhost:$HGPORT\r\n
566 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
566 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
567 readline(-1) -> (2) \r\n
567 readline(-1) -> (2) \r\n
568 write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
568 write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
569 write(23 from 23) -> (120) Server: badhttpserver\r\n
569 write(23 from 23) -> (120) Server: badhttpserver\r\n
570 write(37 from 37) -> (83) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
570 write(37 from 37) -> (83) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
571 write(41 from 41) -> (42) Content-Type: application/mercurial-0.2\r\n
571 write(41 from 41) -> (42) Content-Type: application/mercurial-0.2\r\n
572 write(28 from 28) -> (14) Transfer-Encoding: chunked\r\n
572 write(28 from 28) -> (14) Transfer-Encoding: chunked\r\n
573 write(2 from 2) -> (12) \r\n
573 write(2 from 2) -> (12) \r\n
574 write(6 from 6) -> (6) 1\\r\\n\x04\\r\\n (esc)
574 write(6 from 6) -> (6) 1\\r\\n\x04\\r\\n (esc)
575 write(6 from 9) -> (0) 4\r\nnon
575 write(6 from 9) -> (0) 4\r\nnon
576 write limit reached; closing socket
576 write limit reached; closing socket
577 write(27) -> 15\r\nInternal Server Error\r\n
577 write(27) -> 15\r\nInternal Server Error\r\n
578
578
579 $ rm -f error.log
579 $ rm -f error.log
580
580
581 Server sends partial bundle2 header magic
581 Server sends partial bundle2 header magic
582
582
583 $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
583 $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
584 $ cat hg.pid > $DAEMON_PIDS
584 $ cat hg.pid > $DAEMON_PIDS
585
585
586 $ hg clone http://localhost:$HGPORT/ clone
586 $ hg clone http://localhost:$HGPORT/ clone
587 requesting all changes
587 requesting all changes
588 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
588 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
589 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
589 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
590 [255]
590 [255]
591
591
592 $ killdaemons.py $DAEMON_PIDS
592 $ killdaemons.py $DAEMON_PIDS
593
593
594 $ tail -7 error.log
594 $ tail -7 error.log
595 write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n
595 write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n
596 write(2 from 2) -> (21) \r\n
596 write(2 from 2) -> (21) \r\n
597 write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc)
597 write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc)
598 write(9 from 9) -> (6) 4\r\nnone\r\n
598 write(9 from 9) -> (6) 4\r\nnone\r\n
599 write(6 from 9) -> (0) 4\r\nHG2
599 write(6 from 9) -> (0) 4\r\nHG2
600 write limit reached; closing socket
600 write limit reached; closing socket
601 write(27) -> 15\r\nInternal Server Error\r\n
601 write(27) -> 15\r\nInternal Server Error\r\n
602
602
603 $ rm -f error.log
603 $ rm -f error.log
604
604
605 Server sends incomplete bundle2 stream params length
605 Server sends incomplete bundle2 stream params length
606
606
607 $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
607 $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
608 $ cat hg.pid > $DAEMON_PIDS
608 $ cat hg.pid > $DAEMON_PIDS
609
609
610 $ hg clone http://localhost:$HGPORT/ clone
610 $ hg clone http://localhost:$HGPORT/ clone
611 requesting all changes
611 requesting all changes
612 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
612 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
613 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
613 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
614 [255]
614 [255]
615
615
616 $ killdaemons.py $DAEMON_PIDS
616 $ killdaemons.py $DAEMON_PIDS
617
617
618 $ tail -8 error.log
618 $ tail -8 error.log
619 write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n
619 write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n
620 write(2 from 2) -> (30) \r\n
620 write(2 from 2) -> (30) \r\n
621 write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc)
621 write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc)
622 write(9 from 9) -> (15) 4\r\nnone\r\n
622 write(9 from 9) -> (15) 4\r\nnone\r\n
623 write(9 from 9) -> (6) 4\r\nHG20\r\n
623 write(9 from 9) -> (6) 4\r\nHG20\r\n
624 write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc)
624 write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc)
625 write limit reached; closing socket
625 write limit reached; closing socket
626 write(27) -> 15\r\nInternal Server Error\r\n
626 write(27) -> 15\r\nInternal Server Error\r\n
627
627
628 $ rm -f error.log
628 $ rm -f error.log
629
629
630 Servers stops after bundle2 stream params header
630 Servers stops after bundle2 stream params header
631
631
632 $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
632 $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
633 $ cat hg.pid > $DAEMON_PIDS
633 $ cat hg.pid > $DAEMON_PIDS
634
634
635 $ hg clone http://localhost:$HGPORT/ clone
635 $ hg clone http://localhost:$HGPORT/ clone
636 requesting all changes
636 requesting all changes
637 abort: HTTP request error (incomplete response)
637 abort: HTTP request error (incomplete response)
638 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
638 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
639 [255]
639 [255]
640
640
641 $ killdaemons.py $DAEMON_PIDS
641 $ killdaemons.py $DAEMON_PIDS
642
642
643 $ tail -8 error.log
643 $ tail -8 error.log
644 write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n
644 write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n
645 write(2 from 2) -> (33) \r\n
645 write(2 from 2) -> (33) \r\n
646 write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc)
646 write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc)
647 write(9 from 9) -> (18) 4\r\nnone\r\n
647 write(9 from 9) -> (18) 4\r\nnone\r\n
648 write(9 from 9) -> (9) 4\r\nHG20\r\n
648 write(9 from 9) -> (9) 4\r\nHG20\r\n
649 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
649 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
650 write limit reached; closing socket
650 write limit reached; closing socket
651 write(27) -> 15\r\nInternal Server Error\r\n
651 write(27) -> 15\r\nInternal Server Error\r\n
652
652
653 $ rm -f error.log
653 $ rm -f error.log
654
654
655 Server stops sending after bundle2 part header length
655 Server stops sending after bundle2 part header length
656
656
657 $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
657 $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
658 $ cat hg.pid > $DAEMON_PIDS
658 $ cat hg.pid > $DAEMON_PIDS
659
659
660 $ hg clone http://localhost:$HGPORT/ clone
660 $ hg clone http://localhost:$HGPORT/ clone
661 requesting all changes
661 requesting all changes
662 abort: HTTP request error (incomplete response)
662 abort: HTTP request error (incomplete response)
663 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
663 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
664 [255]
664 [255]
665
665
666 $ killdaemons.py $DAEMON_PIDS
666 $ killdaemons.py $DAEMON_PIDS
667
667
668 $ tail -9 error.log
668 $ tail -9 error.log
669 write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n
669 write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n
670 write(2 from 2) -> (42) \r\n
670 write(2 from 2) -> (42) \r\n
671 write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc)
671 write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc)
672 write(9 from 9) -> (27) 4\r\nnone\r\n
672 write(9 from 9) -> (27) 4\r\nnone\r\n
673 write(9 from 9) -> (18) 4\r\nHG20\r\n
673 write(9 from 9) -> (18) 4\r\nHG20\r\n
674 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
674 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
675 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
675 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
676 write limit reached; closing socket
676 write limit reached; closing socket
677 write(27) -> 15\r\nInternal Server Error\r\n
677 write(27) -> 15\r\nInternal Server Error\r\n
678
678
679 $ rm -f error.log
679 $ rm -f error.log
680
680
681 Server stops sending after bundle2 part header
681 Server stops sending after bundle2 part header
682
682
683 $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
683 $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
684 $ cat hg.pid > $DAEMON_PIDS
684 $ cat hg.pid > $DAEMON_PIDS
685
685
686 $ hg clone http://localhost:$HGPORT/ clone
686 $ hg clone http://localhost:$HGPORT/ clone
687 requesting all changes
687 requesting all changes
688 adding changesets
688 adding changesets
689 transaction abort!
689 transaction abort!
690 rollback completed
690 rollback completed
691 abort: stream ended unexpectedly (got 0 bytes, expected 4)
691 abort: HTTP request error (incomplete response)
692 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
692 [255]
693 [255]
693
694
694 $ killdaemons.py $DAEMON_PIDS
695 $ killdaemons.py $DAEMON_PIDS
695
696
696 $ tail -10 error.log
697 $ tail -10 error.log
697 write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n
698 write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n
698 write(2 from 2) -> (89) \r\n
699 write(2 from 2) -> (89) \r\n
699 write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc)
700 write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc)
700 write(9 from 9) -> (74) 4\r\nnone\r\n
701 write(9 from 9) -> (74) 4\r\nnone\r\n
701 write(9 from 9) -> (65) 4\r\nHG20\r\n
702 write(9 from 9) -> (65) 4\r\nHG20\r\n
702 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
703 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
703 write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
704 write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
704 write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
705 write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
705 write limit reached; closing socket
706 write limit reached; closing socket
706 write(27) -> 15\r\nInternal Server Error\r\n
707 write(27) -> 15\r\nInternal Server Error\r\n
707
708
708 $ rm -f error.log
709 $ rm -f error.log
709
710
710 Server stops after bundle2 part payload chunk size
711 Server stops after bundle2 part payload chunk size
711
712
712 $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
713 $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
713 $ cat hg.pid > $DAEMON_PIDS
714 $ cat hg.pid > $DAEMON_PIDS
714
715
715 $ hg clone http://localhost:$HGPORT/ clone
716 $ hg clone http://localhost:$HGPORT/ clone
716 requesting all changes
717 requesting all changes
717 adding changesets
718 adding changesets
718 transaction abort!
719 transaction abort!
719 rollback completed
720 rollback completed
720 abort: stream ended unexpectedly (got 0 bytes, expected 4)
721 abort: HTTP request error (incomplete response)
722 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
721 [255]
723 [255]
722
724
723 $ killdaemons.py $DAEMON_PIDS
725 $ killdaemons.py $DAEMON_PIDS
724
726
725 $ tail -11 error.log
727 $ tail -11 error.log
726 write(28 from 28) -> (100) Transfer-Encoding: chunked\r\n
728 write(28 from 28) -> (100) Transfer-Encoding: chunked\r\n
727 write(2 from 2) -> (98) \r\n
729 write(2 from 2) -> (98) \r\n
728 write(6 from 6) -> (92) 1\\r\\n\x04\\r\\n (esc)
730 write(6 from 6) -> (92) 1\\r\\n\x04\\r\\n (esc)
729 write(9 from 9) -> (83) 4\r\nnone\r\n
731 write(9 from 9) -> (83) 4\r\nnone\r\n
730 write(9 from 9) -> (74) 4\r\nHG20\r\n
732 write(9 from 9) -> (74) 4\r\nHG20\r\n
731 write(9 from 9) -> (65) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
733 write(9 from 9) -> (65) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
732 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
734 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
733 write(47 from 47) -> (9) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
735 write(47 from 47) -> (9) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
734 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
736 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
735 write limit reached; closing socket
737 write limit reached; closing socket
736 write(27) -> 15\r\nInternal Server Error\r\n
738 write(27) -> 15\r\nInternal Server Error\r\n
737
739
738 $ rm -f error.log
740 $ rm -f error.log
739
741
740 Server stops sending in middle of bundle2 payload chunk
742 Server stops sending in middle of bundle2 payload chunk
741
743
742 $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
744 $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
743 $ cat hg.pid > $DAEMON_PIDS
745 $ cat hg.pid > $DAEMON_PIDS
744
746
745 $ hg clone http://localhost:$HGPORT/ clone
747 $ hg clone http://localhost:$HGPORT/ clone
746 requesting all changes
748 requesting all changes
747 adding changesets
749 adding changesets
748 transaction abort!
750 transaction abort!
749 rollback completed
751 rollback completed
750 abort: stream ended unexpectedly (got 0 bytes, expected 4)
752 abort: HTTP request error (incomplete response)
753 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
751 [255]
754 [255]
752
755
753 $ killdaemons.py $DAEMON_PIDS
756 $ killdaemons.py $DAEMON_PIDS
754
757
755 $ tail -12 error.log
758 $ tail -12 error.log
756 write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n
759 write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n
757 write(2 from 2) -> (571) \r\n
760 write(2 from 2) -> (571) \r\n
758 write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc)
761 write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc)
759 write(9 from 9) -> (556) 4\r\nnone\r\n
762 write(9 from 9) -> (556) 4\r\nnone\r\n
760 write(9 from 9) -> (547) 4\r\nHG20\r\n
763 write(9 from 9) -> (547) 4\r\nHG20\r\n
761 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
764 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
762 write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
765 write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
763 write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
766 write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
764 write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
767 write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
765 write(473 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
768 write(473 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
766 write limit reached; closing socket
769 write limit reached; closing socket
767 write(27) -> 15\r\nInternal Server Error\r\n
770 write(27) -> 15\r\nInternal Server Error\r\n
768
771
769 $ rm -f error.log
772 $ rm -f error.log
770
773
771 Server stops sending after 0 length payload chunk size
774 Server stops sending after 0 length payload chunk size
772
775
773 $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
776 $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
774 $ cat hg.pid > $DAEMON_PIDS
777 $ cat hg.pid > $DAEMON_PIDS
775
778
776 $ hg clone http://localhost:$HGPORT/ clone
779 $ hg clone http://localhost:$HGPORT/ clone
777 requesting all changes
780 requesting all changes
778 adding changesets
781 adding changesets
779 adding manifests
782 adding manifests
780 adding file changes
783 adding file changes
781 added 1 changesets with 1 changes to 1 files
784 added 1 changesets with 1 changes to 1 files
782 transaction abort!
785 transaction abort!
783 rollback completed
786 rollback completed
784 abort: HTTP request error (incomplete response)
787 abort: HTTP request error (incomplete response)
785 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
788 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
786 [255]
789 [255]
787
790
788 $ killdaemons.py $DAEMON_PIDS
791 $ killdaemons.py $DAEMON_PIDS
789
792
790 $ tail -13 error.log
793 $ tail -13 error.log
791 write(28 from 28) -> (582) Transfer-Encoding: chunked\r\n
794 write(28 from 28) -> (582) Transfer-Encoding: chunked\r\n
792 write(2 from 2) -> (580) \r\n
795 write(2 from 2) -> (580) \r\n
793 write(6 from 6) -> (574) 1\\r\\n\x04\\r\\n (esc)
796 write(6 from 6) -> (574) 1\\r\\n\x04\\r\\n (esc)
794 write(9 from 9) -> (565) 4\r\nnone\r\n
797 write(9 from 9) -> (565) 4\r\nnone\r\n
795 write(9 from 9) -> (556) 4\r\nHG20\r\n
798 write(9 from 9) -> (556) 4\r\nHG20\r\n
796 write(9 from 9) -> (547) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
799 write(9 from 9) -> (547) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
797 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
800 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
798 write(47 from 47) -> (491) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
801 write(47 from 47) -> (491) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
799 write(9 from 9) -> (482) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
802 write(9 from 9) -> (482) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
800 write(473 from 473) -> (9) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
803 write(473 from 473) -> (9) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
801 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
804 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
802 write limit reached; closing socket
805 write limit reached; closing socket
803 write(27) -> 15\r\nInternal Server Error\r\n
806 write(27) -> 15\r\nInternal Server Error\r\n
804
807
805 $ rm -f error.log
808 $ rm -f error.log
806
809
807 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
810 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
808 This is before the 0 size chunked transfer part that signals end of HTTP response.
811 This is before the 0 size chunked transfer part that signals end of HTTP response.
809
812
810 $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
813 $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
811 $ cat hg.pid > $DAEMON_PIDS
814 $ cat hg.pid > $DAEMON_PIDS
812
815
813 $ hg clone http://localhost:$HGPORT/ clone
816 $ hg clone http://localhost:$HGPORT/ clone
814 requesting all changes
817 requesting all changes
815 adding changesets
818 adding changesets
816 adding manifests
819 adding manifests
817 adding file changes
820 adding file changes
818 added 1 changesets with 1 changes to 1 files
821 added 1 changesets with 1 changes to 1 files
819 updating to branch default
822 updating to branch default
820 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
823 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
821
824
822 $ killdaemons.py $DAEMON_PIDS
825 $ killdaemons.py $DAEMON_PIDS
823
826
824 $ tail -22 error.log
827 $ tail -22 error.log
825 write(28 from 28) -> (779) Transfer-Encoding: chunked\r\n
828 write(28 from 28) -> (779) Transfer-Encoding: chunked\r\n
826 write(2 from 2) -> (777) \r\n
829 write(2 from 2) -> (777) \r\n
827 write(6 from 6) -> (771) 1\\r\\n\x04\\r\\n (esc)
830 write(6 from 6) -> (771) 1\\r\\n\x04\\r\\n (esc)
828 write(9 from 9) -> (762) 4\r\nnone\r\n
831 write(9 from 9) -> (762) 4\r\nnone\r\n
829 write(9 from 9) -> (753) 4\r\nHG20\r\n
832 write(9 from 9) -> (753) 4\r\nHG20\r\n
830 write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
833 write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
831 write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
834 write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
832 write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
835 write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
833 write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
836 write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
834 write(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
837 write(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
835 write(9 from 9) -> (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
838 write(9 from 9) -> (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
836 write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
839 write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
837 write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
840 write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
838 write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
841 write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
839 write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
842 write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
840 write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
843 write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
841 write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
844 write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
842 write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
845 write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
843 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
846 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
844 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
847 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
845 write limit reached; closing socket
848 write limit reached; closing socket
846 write(27) -> 15\r\nInternal Server Error\r\n
849 write(27) -> 15\r\nInternal Server Error\r\n
847
850
848 $ rm -f error.log
851 $ rm -f error.log
849 $ rm -rf clone
852 $ rm -rf clone
850
853
851 Server sends a size 0 chunked-transfer size without terminating \r\n
854 Server sends a size 0 chunked-transfer size without terminating \r\n
852
855
853 $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
856 $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
854 $ cat hg.pid > $DAEMON_PIDS
857 $ cat hg.pid > $DAEMON_PIDS
855
858
856 $ hg clone http://localhost:$HGPORT/ clone
859 $ hg clone http://localhost:$HGPORT/ clone
857 requesting all changes
860 requesting all changes
858 adding changesets
861 adding changesets
859 adding manifests
862 adding manifests
860 adding file changes
863 adding file changes
861 added 1 changesets with 1 changes to 1 files
864 added 1 changesets with 1 changes to 1 files
862 updating to branch default
865 updating to branch default
863 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
866 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
864
867
865 $ killdaemons.py $DAEMON_PIDS
868 $ killdaemons.py $DAEMON_PIDS
866
869
867 $ tail -23 error.log
870 $ tail -23 error.log
868 write(28 from 28) -> (782) Transfer-Encoding: chunked\r\n
871 write(28 from 28) -> (782) Transfer-Encoding: chunked\r\n
869 write(2 from 2) -> (780) \r\n
872 write(2 from 2) -> (780) \r\n
870 write(6 from 6) -> (774) 1\\r\\n\x04\\r\\n (esc)
873 write(6 from 6) -> (774) 1\\r\\n\x04\\r\\n (esc)
871 write(9 from 9) -> (765) 4\r\nnone\r\n
874 write(9 from 9) -> (765) 4\r\nnone\r\n
872 write(9 from 9) -> (756) 4\r\nHG20\r\n
875 write(9 from 9) -> (756) 4\r\nHG20\r\n
873 write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
876 write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
874 write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
877 write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
875 write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
878 write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
876 write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
879 write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
877 write(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
880 write(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
878 write(9 from 9) -> (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
881 write(9 from 9) -> (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
879 write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
882 write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
880 write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
883 write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
881 write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
884 write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
882 write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
885 write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
883 write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
886 write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
884 write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
887 write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
885 write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
888 write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
886 write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
889 write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
887 write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
890 write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
888 write(3 from 5) -> (0) 0\r\n
891 write(3 from 5) -> (0) 0\r\n
889 write limit reached; closing socket
892 write limit reached; closing socket
890 write(27) -> 15\r\nInternal Server Error\r\n
893 write(27) -> 15\r\nInternal Server Error\r\n
891
894
892 $ rm -f error.log
895 $ rm -f error.log
893 $ rm -rf clone
896 $ rm -rf clone
General Comments 0
You need to be logged in to leave comments. Login now