##// END OF EJS Templates
httppeer: wrap HTTPResponse.read() globally...
Gregory Szorc -
r32002:bf855efe default
parent child Browse files
Show More
@@ -1,254 +1,257
1 1 # error.py - Mercurial exceptions
2 2 #
3 3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Mercurial exceptions.
9 9
10 10 This allows us to catch exceptions at higher levels without forcing
11 11 imports.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 # Do not import anything here, please
17 17
18 18 class Hint(object):
19 19 """Mix-in to provide a hint of an error
20 20
21 21 This should come first in the inheritance list to consume a hint and
22 22 pass remaining arguments to the exception class.
23 23 """
24 24 def __init__(self, *args, **kw):
25 25 self.hint = kw.pop(r'hint', None)
26 26 super(Hint, self).__init__(*args, **kw)
27 27
28 28 class RevlogError(Hint, Exception):
29 29 pass
30 30
31 31 class FilteredIndexError(IndexError):
32 32 pass
33 33
34 34 class LookupError(RevlogError, KeyError):
35 35 def __init__(self, name, index, message):
36 36 self.name = name
37 37 self.index = index
38 38 # this can't be called 'message' because at least some installs of
39 39 # Python 2.6+ complain about the 'message' property being deprecated
40 40 self.lookupmessage = message
41 41 if isinstance(name, str) and len(name) == 20:
42 42 from .node import short
43 43 name = short(name)
44 44 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
45 45
46 46 def __str__(self):
47 47 return RevlogError.__str__(self)
48 48
49 49 class FilteredLookupError(LookupError):
50 50 pass
51 51
52 52 class ManifestLookupError(LookupError):
53 53 pass
54 54
55 55 class CommandError(Exception):
56 56 """Exception raised on errors in parsing the command line."""
57 57
58 58 class InterventionRequired(Hint, Exception):
59 59 """Exception raised when a command requires human intervention."""
60 60
61 61 class Abort(Hint, Exception):
62 62 """Raised if a command needs to print an error and exit."""
63 63
64 64 class HookLoadError(Abort):
65 65 """raised when loading a hook fails, aborting an operation
66 66
67 67 Exists to allow more specialized catching."""
68 68
69 69 class HookAbort(Abort):
70 70 """raised when a validation hook fails, aborting an operation
71 71
72 72 Exists to allow more specialized catching."""
73 73
74 74 class ConfigError(Abort):
75 75 """Exception raised when parsing config files"""
76 76
77 77 class UpdateAbort(Abort):
78 78 """Raised when an update is aborted for destination issue"""
79 79
80 80 class MergeDestAbort(Abort):
81 81 """Raised when an update is aborted for destination issues"""
82 82
83 83 class NoMergeDestAbort(MergeDestAbort):
84 84 """Raised when an update is aborted because there is nothing to merge"""
85 85
86 86 class ManyMergeDestAbort(MergeDestAbort):
87 87 """Raised when an update is aborted because destination is ambiguous"""
88 88
89 89 class ResponseExpected(Abort):
90 90 """Raised when an EOF is received for a prompt"""
91 91 def __init__(self):
92 92 from .i18n import _
93 93 Abort.__init__(self, _('response expected'))
94 94
95 95 class OutOfBandError(Hint, Exception):
96 96 """Exception raised when a remote repo reports failure"""
97 97
98 98 class ParseError(Hint, Exception):
99 99 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
100 100
101 101 class UnknownIdentifier(ParseError):
102 102 """Exception raised when a {rev,file}set references an unknown identifier"""
103 103
104 104 def __init__(self, function, symbols):
105 105 from .i18n import _
106 106 ParseError.__init__(self, _("unknown identifier: %s") % function)
107 107 self.function = function
108 108 self.symbols = symbols
109 109
110 110 class RepoError(Hint, Exception):
111 111 pass
112 112
113 113 class RepoLookupError(RepoError):
114 114 pass
115 115
116 116 class FilteredRepoLookupError(RepoLookupError):
117 117 pass
118 118
119 119 class CapabilityError(RepoError):
120 120 pass
121 121
122 122 class RequirementError(RepoError):
123 123 """Exception raised if .hg/requires has an unknown entry."""
124 124
125 125 class StdioError(IOError):
126 126 """Raised if I/O to stdout or stderr fails"""
127 127
128 128 def __init__(self, err):
129 129 IOError.__init__(self, err.errno, err.strerror)
130 130
131 131 class UnsupportedMergeRecords(Abort):
132 132 def __init__(self, recordtypes):
133 133 from .i18n import _
134 134 self.recordtypes = sorted(recordtypes)
135 135 s = ' '.join(self.recordtypes)
136 136 Abort.__init__(
137 137 self, _('unsupported merge state records: %s') % s,
138 138 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
139 139 'more information'))
140 140
141 141 class LockError(IOError):
142 142 def __init__(self, errno, strerror, filename, desc):
143 143 IOError.__init__(self, errno, strerror, filename)
144 144 self.desc = desc
145 145
146 146 class LockHeld(LockError):
147 147 def __init__(self, errno, filename, desc, locker):
148 148 LockError.__init__(self, errno, 'Lock held', filename, desc)
149 149 self.locker = locker
150 150
151 151 class LockUnavailable(LockError):
152 152 pass
153 153
154 154 # LockError is for errors while acquiring the lock -- this is unrelated
155 155 class LockInheritanceContractViolation(RuntimeError):
156 156 pass
157 157
158 158 class ResponseError(Exception):
159 159 """Raised to print an error with part of output and exit."""
160 160
161 161 class UnknownCommand(Exception):
162 162 """Exception raised if command is not in the command table."""
163 163
164 164 class AmbiguousCommand(Exception):
165 165 """Exception raised if command shortcut matches more than one command."""
166 166
167 167 # derived from KeyboardInterrupt to simplify some breakout code
168 168 class SignalInterrupt(KeyboardInterrupt):
169 169 """Exception raised on SIGTERM and SIGHUP."""
170 170
171 171 class SignatureError(Exception):
172 172 pass
173 173
174 174 class PushRaced(RuntimeError):
175 175 """An exception raised during unbundling that indicate a push race"""
176 176
177 177 class ProgrammingError(RuntimeError):
178 178 """Raised if a mercurial (core or extension) developer made a mistake"""
179 179
180 180 # bundle2 related errors
181 181 class BundleValueError(ValueError):
182 182 """error raised when bundle2 cannot be processed"""
183 183
184 184 class BundleUnknownFeatureError(BundleValueError):
185 185 def __init__(self, parttype=None, params=(), values=()):
186 186 self.parttype = parttype
187 187 self.params = params
188 188 self.values = values
189 189 if self.parttype is None:
190 190 msg = 'Stream Parameter'
191 191 else:
192 192 msg = parttype
193 193 entries = self.params
194 194 if self.params and self.values:
195 195 assert len(self.params) == len(self.values)
196 196 entries = []
197 197 for idx, par in enumerate(self.params):
198 198 val = self.values[idx]
199 199 if val is None:
200 200 entries.append(val)
201 201 else:
202 202 entries.append("%s=%r" % (par, val))
203 203 if entries:
204 204 msg = '%s - %s' % (msg, ', '.join(entries))
205 205 ValueError.__init__(self, msg)
206 206
207 207 class ReadOnlyPartError(RuntimeError):
208 208 """error raised when code tries to alter a part being generated"""
209 209
210 210 class PushkeyFailed(Abort):
211 211 """error raised when a pushkey part failed to update a value"""
212 212
213 213 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
214 214 ret=None):
215 215 self.partid = partid
216 216 self.namespace = namespace
217 217 self.key = key
218 218 self.new = new
219 219 self.old = old
220 220 self.ret = ret
221 221 # no i18n expected to be processed into a better message
222 222 Abort.__init__(self, 'failed to update value for "%s/%s"'
223 223 % (namespace, key))
224 224
225 225 class CensoredNodeError(RevlogError):
226 226 """error raised when content verification fails on a censored node
227 227
228 228 Also contains the tombstone data substituted for the uncensored data.
229 229 """
230 230
231 231 def __init__(self, filename, node, tombstone):
232 232 from .node import short
233 233 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
234 234 self.tombstone = tombstone
235 235
236 236 class CensoredBaseError(RevlogError):
237 237 """error raised when a delta is rejected because its base is censored
238 238
239 239 A delta based on a censored revision must be formed as single patch
240 240 operation which replaces the entire base with new content. This ensures
241 241 the delta may be applied by clones which have not censored the base.
242 242 """
243 243
244 244 class InvalidBundleSpecification(Exception):
245 245 """error raised when a bundle specification is invalid.
246 246
247 247 This is used for syntax errors as opposed to support errors.
248 248 """
249 249
250 250 class UnsupportedBundleSpecification(Exception):
251 251 """error raised when a bundle specification is not supported."""
252 252
253 253 class CorruptedState(Exception):
254 254 """error raised when a command is not able to read its state from file"""
255
256 class RichIOError(Abort):
257 """An IOError that can also have a hint attached."""
@@ -1,383 +1,422
1 1 # httppeer.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import socket
14 14 import struct
15 15 import tempfile
16 16
17 17 from .i18n import _
18 18 from .node import nullid
19 19 from . import (
20 20 bundle2,
21 21 error,
22 22 httpconnection,
23 23 pycompat,
24 24 statichttprepo,
25 25 url,
26 26 util,
27 27 wireproto,
28 28 )
29 29
30 30 httplib = util.httplib
31 31 urlerr = util.urlerr
32 32 urlreq = util.urlreq
33 33
34 34 # FUTURE: consider refactoring this API to use generators. This will
35 35 # require a compression engine API to emit generators.
36 36 def decompressresponse(response, engine):
37 37 try:
38 38 reader = engine.decompressorreader(response)
39 39 except httplib.HTTPException:
40 40 raise IOError(None, _('connection ended unexpectedly'))
41 41
42 42 # We need to wrap reader.read() so HTTPException on subsequent
43 43 # reads is also converted.
44 44 # Ideally we'd use super() here. However, if ``reader`` isn't a new-style
45 45 # class, this can raise:
46 46 # TypeError: super() argument 1 must be type, not classobj
47 47 origread = reader.read
48 48 class readerproxy(reader.__class__):
49 49 def read(self, *args, **kwargs):
50 50 try:
51 51 return origread(*args, **kwargs)
52 52 except httplib.HTTPException:
53 53 raise IOError(None, _('connection ended unexpectedly'))
54 54
55 55 reader.__class__ = readerproxy
56 56 return reader
57 57
58 58 def encodevalueinheaders(value, header, limit):
59 59 """Encode a string value into multiple HTTP headers.
60 60
61 61 ``value`` will be encoded into 1 or more HTTP headers with the names
62 62 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
63 63 name + value will be at most ``limit`` bytes long.
64 64
65 65 Returns an iterable of 2-tuples consisting of header names and values.
66 66 """
67 67 fmt = header + '-%s'
68 68 valuelen = limit - len(fmt % '000') - len(': \r\n')
69 69 result = []
70 70
71 71 n = 0
72 72 for i in xrange(0, len(value), valuelen):
73 73 n += 1
74 74 result.append((fmt % str(n), value[i:i + valuelen]))
75 75
76 76 return result
77 77
78 def _wraphttpresponse(resp):
79 """Wrap an HTTPResponse with common error handlers.
80
81 This ensures that any I/O from any consumer raises the appropriate
82 error and messaging.
83 """
84 origread = resp.read
85
86 class readerproxy(resp.__class__):
87 def read(self, size=None):
88 try:
89 return origread(size)
90 except httplib.IncompleteRead as e:
91 # e.expected is an integer if length known or None otherwise.
92 if e.expected:
93 msg = _('HTTP request error (incomplete response; '
94 'expected %d bytes got %d)') % (e.expected,
95 len(e.partial))
96 else:
97 msg = _('HTTP request error (incomplete response)')
98
99 raise error.RichIOError(
100 msg,
101 hint=_('this may be an intermittent network failure; '
102 'if the error persists, consider contacting the '
103 'network or server operator'))
104 except httplib.HTTPException as e:
105 raise error.RichIOError(
106 _('HTTP request error (%s)') % e,
107 hint=_('this may be an intermittent failure; '
108 'if the error persists, consider contacting the '
109 'network or server operator'))
110
111 resp.__class__ = readerproxy
112
78 113 class httppeer(wireproto.wirepeer):
79 114 def __init__(self, ui, path):
80 115 self.path = path
81 116 self.caps = None
82 117 self.handler = None
83 118 self.urlopener = None
84 119 self.requestbuilder = None
85 120 u = util.url(path)
86 121 if u.query or u.fragment:
87 122 raise error.Abort(_('unsupported URL component: "%s"') %
88 123 (u.query or u.fragment))
89 124
90 125 # urllib cannot handle URLs with embedded user or passwd
91 126 self._url, authinfo = u.authinfo()
92 127
93 128 self.ui = ui
94 129 self.ui.debug('using %s\n' % self._url)
95 130
96 131 self.urlopener = url.opener(ui, authinfo)
97 132 self.requestbuilder = urlreq.request
98 133
99 134 def __del__(self):
100 135 urlopener = getattr(self, 'urlopener', None)
101 136 if urlopener:
102 137 for h in urlopener.handlers:
103 138 h.close()
104 139 getattr(h, "close_all", lambda : None)()
105 140
106 141 def url(self):
107 142 return self.path
108 143
109 144 # look up capabilities only when needed
110 145
111 146 def _fetchcaps(self):
112 147 self.caps = set(self._call('capabilities').split())
113 148
114 149 def _capabilities(self):
115 150 if self.caps is None:
116 151 try:
117 152 self._fetchcaps()
118 153 except error.RepoError:
119 154 self.caps = set()
120 155 self.ui.debug('capabilities: %s\n' %
121 156 (' '.join(self.caps or ['none'])))
122 157 return self.caps
123 158
124 159 def lock(self):
125 160 raise error.Abort(_('operation not supported over http'))
126 161
127 162 def _callstream(self, cmd, _compressible=False, **args):
128 163 if cmd == 'pushkey':
129 164 args['data'] = ''
130 165 data = args.pop('data', None)
131 166 headers = args.pop('headers', {})
132 167
133 168 self.ui.debug("sending %s command\n" % cmd)
134 169 q = [('cmd', cmd)]
135 170 headersize = 0
136 171 varyheaders = []
137 172 # Important: don't use self.capable() here or else you end up
138 173 # with infinite recursion when trying to look up capabilities
139 174 # for the first time.
140 175 postargsok = self.caps is not None and 'httppostargs' in self.caps
141 176 # TODO: support for httppostargs when data is a file-like
142 177 # object rather than a basestring
143 178 canmungedata = not data or isinstance(data, basestring)
144 179 if postargsok and canmungedata:
145 180 strargs = urlreq.urlencode(sorted(args.items()))
146 181 if strargs:
147 182 if not data:
148 183 data = strargs
149 184 elif isinstance(data, basestring):
150 185 data = strargs + data
151 186 headers['X-HgArgs-Post'] = len(strargs)
152 187 else:
153 188 if len(args) > 0:
154 189 httpheader = self.capable('httpheader')
155 190 if httpheader:
156 191 headersize = int(httpheader.split(',', 1)[0])
157 192 if headersize > 0:
158 193 # The headers can typically carry more data than the URL.
159 194 encargs = urlreq.urlencode(sorted(args.items()))
160 195 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
161 196 headersize):
162 197 headers[header] = value
163 198 varyheaders.append(header)
164 199 else:
165 200 q += sorted(args.items())
166 201 qs = '?%s' % urlreq.urlencode(q)
167 202 cu = "%s%s" % (self._url, qs)
168 203 size = 0
169 204 if util.safehasattr(data, 'length'):
170 205 size = data.length
171 206 elif data is not None:
172 207 size = len(data)
173 208 if size and self.ui.configbool('ui', 'usehttp2', False):
174 209 headers['Expect'] = '100-Continue'
175 210 headers['X-HgHttp2'] = '1'
176 211 if data is not None and 'Content-Type' not in headers:
177 212 headers['Content-Type'] = 'application/mercurial-0.1'
178 213
179 214 # Tell the server we accept application/mercurial-0.2 and multiple
180 215 # compression formats if the server is capable of emitting those
181 216 # payloads.
182 217 protoparams = []
183 218
184 219 mediatypes = set()
185 220 if self.caps is not None:
186 221 mt = self.capable('httpmediatype')
187 222 if mt:
188 223 protoparams.append('0.1')
189 224 mediatypes = set(mt.split(','))
190 225
191 226 if '0.2tx' in mediatypes:
192 227 protoparams.append('0.2')
193 228
194 229 if '0.2tx' in mediatypes and self.capable('compression'):
195 230 # We /could/ compare supported compression formats and prune
196 231 # non-mutually supported or error if nothing is mutually supported.
197 232 # For now, send the full list to the server and have it error.
198 233 comps = [e.wireprotosupport().name for e in
199 234 util.compengines.supportedwireengines(util.CLIENTROLE)]
200 235 protoparams.append('comp=%s' % ','.join(comps))
201 236
202 237 if protoparams:
203 238 protoheaders = encodevalueinheaders(' '.join(protoparams),
204 239 'X-HgProto',
205 240 headersize or 1024)
206 241 for header, value in protoheaders:
207 242 headers[header] = value
208 243 varyheaders.append(header)
209 244
210 245 headers['Vary'] = ','.join(varyheaders)
211 246 req = self.requestbuilder(cu, data, headers)
212 247
213 248 if data is not None:
214 249 self.ui.debug("sending %s bytes\n" % size)
215 250 req.add_unredirected_header('Content-Length', '%d' % size)
216 251 try:
217 252 resp = self.urlopener.open(req)
218 253 except urlerr.httperror as inst:
219 254 if inst.code == 401:
220 255 raise error.Abort(_('authorization failed'))
221 256 raise
222 257 except httplib.HTTPException as inst:
223 258 self.ui.debug('http error while sending %s command\n' % cmd)
224 259 self.ui.traceback()
225 260 raise IOError(None, inst)
261
262 # Insert error handlers for common I/O failures.
263 _wraphttpresponse(resp)
264
226 265 # record the url we got redirected to
227 266 resp_url = resp.geturl()
228 267 if resp_url.endswith(qs):
229 268 resp_url = resp_url[:-len(qs)]
230 269 if self._url.rstrip('/') != resp_url.rstrip('/'):
231 270 if not self.ui.quiet:
232 271 self.ui.warn(_('real URL is %s\n') % resp_url)
233 272 self._url = resp_url
234 273 try:
235 274 proto = resp.getheader('content-type')
236 275 except AttributeError:
237 276 proto = resp.headers.get('content-type', '')
238 277
239 278 safeurl = util.hidepassword(self._url)
240 279 if proto.startswith('application/hg-error'):
241 280 raise error.OutOfBandError(resp.read())
242 281 # accept old "text/plain" and "application/hg-changegroup" for now
243 282 if not (proto.startswith('application/mercurial-') or
244 283 (proto.startswith('text/plain')
245 284 and not resp.headers.get('content-length')) or
246 285 proto.startswith('application/hg-changegroup')):
247 286 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
248 287 raise error.RepoError(
249 288 _("'%s' does not appear to be an hg repository:\n"
250 289 "---%%<--- (%s)\n%s\n---%%<---\n")
251 290 % (safeurl, proto or 'no content-type', resp.read(1024)))
252 291
253 292 if proto.startswith('application/mercurial-'):
254 293 try:
255 294 version = proto.split('-', 1)[1]
256 295 version_info = tuple([int(n) for n in version.split('.')])
257 296 except ValueError:
258 297 raise error.RepoError(_("'%s' sent a broken Content-Type "
259 298 "header (%s)") % (safeurl, proto))
260 299
261 300 if version_info == (0, 1):
262 301 if _compressible:
263 302 return decompressresponse(resp, util.compengines['zlib'])
264 303 return resp
265 304 elif version_info == (0, 2):
266 305 # application/mercurial-0.2 always identifies the compression
267 306 # engine in the payload header.
268 307 elen = struct.unpack('B', resp.read(1))[0]
269 308 ename = resp.read(elen)
270 309 engine = util.compengines.forwiretype(ename)
271 310 return decompressresponse(resp, engine)
272 311 else:
273 312 raise error.RepoError(_("'%s' uses newer protocol %s") %
274 313 (safeurl, version))
275 314
276 315 if _compressible:
277 316 return decompressresponse(resp, util.compengines['zlib'])
278 317
279 318 return resp
280 319
281 320 def _call(self, cmd, **args):
282 321 fp = self._callstream(cmd, **args)
283 322 try:
284 323 return fp.read()
285 324 finally:
286 325 # if using keepalive, allow connection to be reused
287 326 fp.close()
288 327
289 328 def _callpush(self, cmd, cg, **args):
290 329 # have to stream bundle to a temp file because we do not have
291 330 # http 1.1 chunked transfer.
292 331
293 332 types = self.capable('unbundle')
294 333 try:
295 334 types = types.split(',')
296 335 except AttributeError:
297 336 # servers older than d1b16a746db6 will send 'unbundle' as a
298 337 # boolean capability. They only support headerless/uncompressed
299 338 # bundles.
300 339 types = [""]
301 340 for x in types:
302 341 if x in bundle2.bundletypes:
303 342 type = x
304 343 break
305 344
306 345 tempname = bundle2.writebundle(self.ui, cg, None, type)
307 346 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
308 347 headers = {'Content-Type': 'application/mercurial-0.1'}
309 348
310 349 try:
311 350 r = self._call(cmd, data=fp, headers=headers, **args)
312 351 vals = r.split('\n', 1)
313 352 if len(vals) < 2:
314 353 raise error.ResponseError(_("unexpected response:"), r)
315 354 return vals
316 355 except socket.error as err:
317 356 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
318 357 raise error.Abort(_('push failed: %s') % err.args[1])
319 358 raise error.Abort(err.args[1])
320 359 finally:
321 360 fp.close()
322 361 os.unlink(tempname)
323 362
324 363 def _calltwowaystream(self, cmd, fp, **args):
325 364 fh = None
326 365 fp_ = None
327 366 filename = None
328 367 try:
329 368 # dump bundle to disk
330 369 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
331 370 fh = os.fdopen(fd, pycompat.sysstr("wb"))
332 371 d = fp.read(4096)
333 372 while d:
334 373 fh.write(d)
335 374 d = fp.read(4096)
336 375 fh.close()
337 376 # start http push
338 377 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
339 378 headers = {'Content-Type': 'application/mercurial-0.1'}
340 379 return self._callstream(cmd, data=fp_, headers=headers, **args)
341 380 finally:
342 381 if fp_ is not None:
343 382 fp_.close()
344 383 if fh is not None:
345 384 fh.close()
346 385 os.unlink(filename)
347 386
348 387 def _callcompressable(self, cmd, **args):
349 388 return self._callstream(cmd, _compressible=True, **args)
350 389
351 390 def _abort(self, exception):
352 391 raise exception
353 392
354 393 class httpspeer(httppeer):
355 394 def __init__(self, ui, path):
356 395 if not url.has_https:
357 396 raise error.Abort(_('Python support for SSL and HTTPS '
358 397 'is not installed'))
359 398 httppeer.__init__(self, ui, path)
360 399
361 400 def instance(ui, path, create):
362 401 if create:
363 402 raise error.Abort(_('cannot create new http repository'))
364 403 try:
365 404 if path.startswith('https:'):
366 405 inst = httpspeer(ui, path)
367 406 else:
368 407 inst = httppeer(ui, path)
369 408 try:
370 409 # Try to do useful work when checking compatibility.
371 410 # Usually saves a roundtrip since we want the caps anyway.
372 411 inst._fetchcaps()
373 412 except error.RepoError:
374 413 # No luck, try older compatibility check.
375 414 inst.between([(nullid, nullid)])
376 415 return inst
377 416 except error.RepoError as httpexception:
378 417 try:
379 418 r = statichttprepo.instance(ui, "static-" + path, create)
380 419 ui.note(_('(falling back to static-http)\n'))
381 420 return r
382 421 except error.RepoError:
383 422 raise httpexception # use the original http RepoError instead
@@ -1,896 +1,901
1 1 #require killdaemons serve zstd
2 2
3 3 Client version is embedded in HTTP request and is effectively dynamic. Pin the
4 4 version so behavior is deterministic.
5 5
6 6 $ cat > fakeversion.py << EOF
7 7 > from mercurial import util
8 8 > util.version = lambda: '4.2'
9 9 > EOF
10 10
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [extensions]
13 13 > fakeversion = `pwd`/fakeversion.py
14 14 > EOF
15 15
16 16 $ hg init server0
17 17 $ cd server0
18 18 $ touch foo
19 19 $ hg -q commit -A -m initial
20 20
21 21 Also disable compression because zstd is optional and causes output to vary
22 22 and because debugging partial responses is hard when compression is involved
23 23
24 24 $ cat > .hg/hgrc << EOF
25 25 > [extensions]
26 26 > badserver = $TESTDIR/badserverext.py
27 27 > [server]
28 28 > compressionengines = none
29 29 > EOF
30 30
31 31 Failure to accept() socket should result in connection related error message
32 32
33 33 $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
34 34 $ cat hg.pid > $DAEMON_PIDS
35 35
36 36 $ hg clone http://localhost:$HGPORT/ clone
37 37 abort: error: Connection reset by peer
38 38 [255]
39 39
40 40 (The server exits on its own, but there is a race between that and starting a new server.
41 41 So ensure the process is dead.)
42 42
43 43 $ killdaemons.py $DAEMON_PIDS
44 44
45 45 Failure immediately after accept() should yield connection related error message
46 46
47 47 $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
48 48 $ cat hg.pid > $DAEMON_PIDS
49 49
50 50 $ hg clone http://localhost:$HGPORT/ clone
51 51 abort: error: Connection reset by peer
52 52 [255]
53 53
54 54 $ killdaemons.py $DAEMON_PIDS
55 55
56 56 Failure to read all bytes in initial HTTP request should yield connection related error message
57 57
58 58 $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
59 59 $ cat hg.pid > $DAEMON_PIDS
60 60
61 61 TODO this error message is not very good
62 62
63 63 $ hg clone http://localhost:$HGPORT/ clone
64 64 abort: error: ''
65 65 [255]
66 66
67 67 $ killdaemons.py $DAEMON_PIDS
68 68
69 69 $ cat error.log
70 70 readline(1 from 65537) -> (1) G
71 71 read limit reached; closing socket
72 72
73 73 $ rm -f error.log
74 74
75 75 Same failure, but server reads full HTTP request line
76 76
77 77 $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
78 78 $ cat hg.pid > $DAEMON_PIDS
79 79 $ hg clone http://localhost:$HGPORT/ clone
80 80 abort: error: ''
81 81 [255]
82 82
83 83 $ killdaemons.py $DAEMON_PIDS
84 84
85 85 $ cat error.log
86 86 readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
87 87 readline(7 from -1) -> (7) Accept-
88 88 read limit reached; closing socket
89 89
90 90 $ rm -f error.log
91 91
92 92 Failure on subsequent HTTP request on the same socket (cmd?batch)
93 93
94 94 $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
95 95 $ cat hg.pid > $DAEMON_PIDS
96 96 $ hg clone http://localhost:$HGPORT/ clone
97 97 abort: error: ''
98 98 [255]
99 99
100 100 $ killdaemons.py $DAEMON_PIDS
101 101
102 102 $ cat error.log
103 103 readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
104 104 readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
105 105 readline(150 from -1) -> (8) vary: \r\n
106 106 readline(142 from -1) -> (35) accept: application/mercurial-0.1\r\n
107 107 readline(107 from -1) -> (23) host: localhost:$HGPORT\r\n
108 108 readline(84 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
109 109 readline(35 from -1) -> (2) \r\n
110 110 write(36) -> HTTP/1.1 200 Script output follows\r\n
111 111 write(23) -> Server: badhttpserver\r\n
112 112 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
113 113 write(41) -> Content-Type: application/mercurial-0.1\r\n
114 114 write(21) -> Content-Length: 405\r\n
115 115 write(2) -> \r\n
116 116 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 117 readline(33 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
118 118 readline(7 from -1) -> (7) Accept-
119 119 read limit reached; closing socket
120 120 readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
121 121 readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
122 122 readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
123 123 readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
124 124 readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
125 125 readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
126 126 readline(4 from -1) -> (4) host
127 127 read limit reached; closing socket
128 128
129 129 $ rm -f error.log
130 130
131 131 Failure to read getbundle HTTP request
132 132
133 133 $ hg --config badserver.closeafterrecvbytes=300 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
134 134 $ cat hg.pid > $DAEMON_PIDS
135 135 $ hg clone http://localhost:$HGPORT/ clone
136 136 requesting all changes
137 137 abort: error: ''
138 138 [255]
139 139
140 140 $ killdaemons.py $DAEMON_PIDS
141 141
142 142 $ cat error.log
143 143 readline(300 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
144 144 readline(267 from -1) -> (27) Accept-Encoding: identity\r\n
145 145 readline(240 from -1) -> (8) vary: \r\n
146 146 readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
147 147 readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
148 148 readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
149 149 readline(125 from -1) -> (2) \r\n
150 150 write(36) -> HTTP/1.1 200 Script output follows\r\n
151 151 write(23) -> Server: badhttpserver\r\n
152 152 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
153 153 write(41) -> Content-Type: application/mercurial-0.1\r\n
154 154 write(21) -> Content-Length: 405\r\n
155 155 write(2) -> \r\n
156 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 157 readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
158 158 readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
159 159 readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
160 160 readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
161 161 read limit reached; closing socket
162 162 readline(300 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
163 163 readline(274 from -1) -> (27) Accept-Encoding: identity\r\n
164 164 readline(247 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
165 165 readline(218 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
166 166 readline(177 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
167 167 readline(129 from -1) -> (35) accept: application/mercurial-0.1\r\n
168 168 readline(94 from -1) -> (23) host: localhost:$HGPORT\r\n
169 169 readline(71 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
170 170 readline(22 from -1) -> (2) \r\n
171 171 write(36) -> HTTP/1.1 200 Script output follows\r\n
172 172 write(23) -> Server: badhttpserver\r\n
173 173 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
174 174 write(41) -> Content-Type: application/mercurial-0.1\r\n
175 175 write(20) -> Content-Length: 42\r\n
176 176 write(2) -> \r\n
177 177 write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
178 178 readline(20 from 65537) -> (20) GET /?cmd=getbundle
179 179 read limit reached; closing socket
180 180 readline(300 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
181 181 readline(270 from -1) -> (27) Accept-Encoding: identity\r\n
182 182 readline(243 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
183 183 readline(214 from -1) -> (214) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%2
184 184 read limit reached; closing socket
185 185
186 186 $ rm -f error.log
187 187
188 188 Now do a variation using POST to send arguments
189 189
190 190 $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
191 191 $ cat hg.pid > $DAEMON_PIDS
192 192
193 193 $ hg clone http://localhost:$HGPORT/ clone
194 194 abort: error: ''
195 195 [255]
196 196
197 197 $ killdaemons.py $DAEMON_PIDS
198 198
199 199 $ cat error.log
200 200 readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
201 201 readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
202 202 readline(255 from -1) -> (8) vary: \r\n
203 203 readline(247 from -1) -> (35) accept: application/mercurial-0.1\r\n
204 204 readline(212 from -1) -> (23) host: localhost:$HGPORT\r\n
205 205 readline(189 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
206 206 readline(140 from -1) -> (2) \r\n
207 207 write(36) -> HTTP/1.1 200 Script output follows\r\n
208 208 write(23) -> Server: badhttpserver\r\n
209 209 write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
210 210 write(41) -> Content-Type: application/mercurial-0.1\r\n
211 211 write(21) -> Content-Length: 418\r\n
212 212 write(2) -> \r\n
213 213 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
214 214 readline(138 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
215 215 readline(111 from -1) -> (27) Accept-Encoding: identity\r\n
216 216 readline(84 from -1) -> (41) content-type: application/mercurial-0.1\r\n
217 217 readline(43 from -1) -> (19) vary: X-HgProto-1\r\n
218 218 readline(24 from -1) -> (19) x-hgargs-post: 28\r\n
219 219 readline(5 from -1) -> (5) x-hgp
220 220 read limit reached; closing socket
221 221 readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
222 222 readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
223 223 readline(261 from -1) -> (41) content-type: application/mercurial-0.1\r\n
224 224 readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
225 225 readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
226 226 readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
227 227 readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
228 228 readline(99 from -1) -> (20) content-length: 28\r\n
229 229 readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
230 230 readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
231 231 readline(7 from -1) -> (2) \r\n
232 232 read(5 from 28) -> (5) cmds=
233 233 read limit reached, closing socket
234 234 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
235 235
236 236 $ rm -f error.log
237 237
238 238 Now move on to partial server responses
239 239
240 240 Server sends a single character from the HTTP response line
241 241
242 242 $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
243 243 $ cat hg.pid > $DAEMON_PIDS
244 244
245 245 $ hg clone http://localhost:$HGPORT/ clone
246 246 abort: error: H
247 247 [255]
248 248
249 249 $ killdaemons.py $DAEMON_PIDS
250 250
251 251 $ cat error.log
252 252 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
253 253 readline(-1) -> (27) Accept-Encoding: identity\r\n
254 254 readline(-1) -> (8) vary: \r\n
255 255 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
256 256 readline(-1) -> (23) host: localhost:$HGPORT\r\n
257 257 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
258 258 readline(-1) -> (2) \r\n
259 259 write(1 from 36) -> (0) H
260 260 write limit reached; closing socket
261 261 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
262 262
263 263 $ rm -f error.log
264 264
265 265 Server sends an incomplete capabilities response body
266 266
267 267 $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
268 268 $ cat hg.pid > $DAEMON_PIDS
269 269
270 TODO client spews a stack due to uncaught httplib.IncompleteRead
271
272 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
273 [1]
270 $ hg clone http://localhost:$HGPORT/ clone
271 abort: HTTP request error (incomplete response; expected 385 bytes got 20)
272 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
273 [255]
274 274
275 275 $ killdaemons.py $DAEMON_PIDS
276 276
277 277 $ cat error.log
278 278 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
279 279 readline(-1) -> (27) Accept-Encoding: identity\r\n
280 280 readline(-1) -> (8) vary: \r\n
281 281 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
282 282 readline(-1) -> (23) host: localhost:$HGPORT\r\n
283 283 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
284 284 readline(-1) -> (2) \r\n
285 285 write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
286 286 write(23 from 23) -> (121) Server: badhttpserver\r\n
287 287 write(37 from 37) -> (84) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
288 288 write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
289 289 write(21 from 21) -> (22) Content-Length: 405\r\n
290 290 write(2 from 2) -> (20) \r\n
291 291 write(20 from 405) -> (0) lookup changegroupsu
292 292 write limit reached; closing socket
293 293
294 294 $ rm -f error.log
295 295
296 296 Server sends incomplete headers for batch request
297 297
298 298 $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
299 299 $ cat hg.pid > $DAEMON_PIDS
300 300
301 301 TODO this output is horrible
302 302
303 303 $ hg clone http://localhost:$HGPORT/ clone
304 304 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
305 305 ---%<--- (application/mercuria)
306 306
307 307 ---%<---
308 308 !
309 309 [255]
310 310
311 311 $ killdaemons.py $DAEMON_PIDS
312 312
313 313 $ cat error.log
314 314 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
315 315 readline(-1) -> (27) Accept-Encoding: identity\r\n
316 316 readline(-1) -> (8) vary: \r\n
317 317 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
318 318 readline(-1) -> (23) host: localhost:$HGPORT\r\n
319 319 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
320 320 readline(-1) -> (2) \r\n
321 321 write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
322 322 write(23 from 23) -> (636) Server: badhttpserver\r\n
323 323 write(37 from 37) -> (599) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
324 324 write(41 from 41) -> (558) Content-Type: application/mercurial-0.1\r\n
325 325 write(21 from 21) -> (537) Content-Length: 405\r\n
326 326 write(2 from 2) -> (535) \r\n
327 327 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
328 328 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
329 329 readline(-1) -> (27) Accept-Encoding: identity\r\n
330 330 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
331 331 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
332 332 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
333 333 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
334 334 readline(-1) -> (23) host: localhost:$HGPORT\r\n
335 335 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
336 336 readline(-1) -> (2) \r\n
337 337 write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
338 338 write(23 from 23) -> (71) Server: badhttpserver\r\n
339 339 write(37 from 37) -> (34) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
340 340 write(34 from 41) -> (0) Content-Type: application/mercuria
341 341 write limit reached; closing socket
342 342 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
343 343
344 344 $ rm -f error.log
345 345
346 346 Server sends an incomplete HTTP response body to batch request
347 347
348 348 $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
349 349 $ cat hg.pid > $DAEMON_PIDS
350 350
351 351 TODO client spews a stack due to uncaught ValueError in batch.results()
352 352 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
353 353 [1]
354 354
355 355 $ killdaemons.py $DAEMON_PIDS
356 356
357 357 $ cat error.log
358 358 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
359 359 readline(-1) -> (27) Accept-Encoding: identity\r\n
360 360 readline(-1) -> (8) vary: \r\n
361 361 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
362 362 readline(-1) -> (23) host: localhost:$HGPORT\r\n
363 363 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
364 364 readline(-1) -> (2) \r\n
365 365 write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
366 366 write(23 from 23) -> (701) Server: badhttpserver\r\n
367 367 write(37 from 37) -> (664) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
368 368 write(41 from 41) -> (623) Content-Type: application/mercurial-0.1\r\n
369 369 write(21 from 21) -> (602) Content-Length: 405\r\n
370 370 write(2 from 2) -> (600) \r\n
371 371 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
372 372 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
373 373 readline(-1) -> (27) Accept-Encoding: identity\r\n
374 374 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
375 375 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
376 376 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
377 377 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
378 378 readline(-1) -> (23) host: localhost:$HGPORT\r\n
379 379 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
380 380 readline(-1) -> (2) \r\n
381 381 write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
382 382 write(23 from 23) -> (136) Server: badhttpserver\r\n
383 383 write(37 from 37) -> (99) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
384 384 write(41 from 41) -> (58) Content-Type: application/mercurial-0.1\r\n
385 385 write(20 from 20) -> (38) Content-Length: 42\r\n
386 386 write(2 from 2) -> (36) \r\n
387 387 write(36 from 42) -> (0) 96ee1d7354c4ad7372047672c36a1f561e3a
388 388 write limit reached; closing socket
389 389
390 390 $ rm -f error.log
391 391
392 392 Server sends incomplete headers for getbundle response
393 393
394 394 $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
395 395 $ cat hg.pid > $DAEMON_PIDS
396 396
397 397 TODO this output is terrible
398 398
399 399 $ hg clone http://localhost:$HGPORT/ clone
400 400 requesting all changes
401 401 abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
402 402 ---%<--- (application/mercuri)
403 403
404 404 ---%<---
405 405 !
406 406 [255]
407 407
408 408 $ killdaemons.py $DAEMON_PIDS
409 409
410 410 $ cat error.log
411 411 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
412 412 readline(-1) -> (27) Accept-Encoding: identity\r\n
413 413 readline(-1) -> (8) vary: \r\n
414 414 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
415 415 readline(-1) -> (23) host: localhost:$HGPORT\r\n
416 416 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
417 417 readline(-1) -> (2) \r\n
418 418 write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
419 419 write(23 from 23) -> (836) Server: badhttpserver\r\n
420 420 write(37 from 37) -> (799) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
421 421 write(41 from 41) -> (758) Content-Type: application/mercurial-0.1\r\n
422 422 write(21 from 21) -> (737) Content-Length: 405\r\n
423 423 write(2 from 2) -> (735) \r\n
424 424 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
425 425 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
426 426 readline(-1) -> (27) Accept-Encoding: identity\r\n
427 427 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
428 428 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
429 429 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
430 430 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
431 431 readline(-1) -> (23) host: localhost:$HGPORT\r\n
432 432 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
433 433 readline(-1) -> (2) \r\n
434 434 write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
435 435 write(23 from 23) -> (271) Server: badhttpserver\r\n
436 436 write(37 from 37) -> (234) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
437 437 write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n
438 438 write(20 from 20) -> (173) Content-Length: 42\r\n
439 439 write(2 from 2) -> (171) \r\n
440 440 write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
441 441 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
442 442 readline(-1) -> (27) Accept-Encoding: identity\r\n
443 443 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
444 444 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
445 445 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
446 446 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
447 447 readline(-1) -> (23) host: localhost:$HGPORT\r\n
448 448 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
449 449 readline(-1) -> (2) \r\n
450 450 write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
451 451 write(23 from 23) -> (70) Server: badhttpserver\r\n
452 452 write(37 from 37) -> (33) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
453 453 write(33 from 41) -> (0) Content-Type: application/mercuri
454 454 write limit reached; closing socket
455 455 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
456 456
457 457 $ rm -f error.log
458 458
459 459 Server sends empty HTTP body for getbundle
460 460
461 461 $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
462 462 $ cat hg.pid > $DAEMON_PIDS
463 463
464 TODO client spews a stack due to uncaught httplib.IncompleteRead
465
466 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
464 $ hg clone http://localhost:$HGPORT/ clone
467 465 requesting all changes
468 [1]
466 abort: HTTP request error (incomplete response)
467 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
468 [255]
469 469
470 470 $ killdaemons.py $DAEMON_PIDS
471 471
472 472 $ cat error.log
473 473 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
474 474 readline(-1) -> (27) Accept-Encoding: identity\r\n
475 475 readline(-1) -> (8) vary: \r\n
476 476 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
477 477 readline(-1) -> (23) host: localhost:$HGPORT\r\n
478 478 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
479 479 readline(-1) -> (2) \r\n
480 480 write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
481 481 write(23 from 23) -> (874) Server: badhttpserver\r\n
482 482 write(37 from 37) -> (837) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
483 483 write(41 from 41) -> (796) Content-Type: application/mercurial-0.1\r\n
484 484 write(21 from 21) -> (775) Content-Length: 405\r\n
485 485 write(2 from 2) -> (773) \r\n
486 486 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
487 487 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
488 488 readline(-1) -> (27) Accept-Encoding: identity\r\n
489 489 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
490 490 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
491 491 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
492 492 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
493 493 readline(-1) -> (23) host: localhost:$HGPORT\r\n
494 494 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
495 495 readline(-1) -> (2) \r\n
496 496 write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
497 497 write(23 from 23) -> (309) Server: badhttpserver\r\n
498 498 write(37 from 37) -> (272) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
499 499 write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n
500 500 write(20 from 20) -> (211) Content-Length: 42\r\n
501 501 write(2 from 2) -> (209) \r\n
502 502 write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
503 503 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
504 504 readline(-1) -> (27) Accept-Encoding: identity\r\n
505 505 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
506 506 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
507 507 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
508 508 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
509 509 readline(-1) -> (23) host: localhost:$HGPORT\r\n
510 510 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
511 511 readline(-1) -> (2) \r\n
512 512 write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
513 513 write(23 from 23) -> (108) Server: badhttpserver\r\n
514 514 write(37 from 37) -> (71) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
515 515 write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n
516 516 write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n
517 517 write(2 from 2) -> (0) \r\n
518 518 write limit reached; closing socket
519 519 write(36) -> HTTP/1.1 500 Internal Server Error\r\n
520 520
521 521 $ rm -f error.log
522 522
523 523 Server sends partial compression string
524 524
525 525 $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
526 526 $ cat hg.pid > $DAEMON_PIDS
527 527
528 TODO client spews a stack due to uncaught httplib.IncompleteRead
529
530 $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
528 $ hg clone http://localhost:$HGPORT/ clone
531 529 requesting all changes
532 [1]
530 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
531 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
532 [255]
533 533
534 534 $ killdaemons.py $DAEMON_PIDS
535 535
536 536 $ cat error.log
537 537 readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
538 538 readline(-1) -> (27) Accept-Encoding: identity\r\n
539 539 readline(-1) -> (8) vary: \r\n
540 540 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
541 541 readline(-1) -> (23) host: localhost:$HGPORT\r\n
542 542 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
543 543 readline(-1) -> (2) \r\n
544 544 write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
545 545 write(23 from 23) -> (886) Server: badhttpserver\r\n
546 546 write(37 from 37) -> (849) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
547 547 write(41 from 41) -> (808) Content-Type: application/mercurial-0.1\r\n
548 548 write(21 from 21) -> (787) Content-Length: 405\r\n
549 549 write(2 from 2) -> (785) \r\n
550 550 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
551 551 readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
552 552 readline(-1) -> (27) Accept-Encoding: identity\r\n
553 553 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
554 554 readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
555 555 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
556 556 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
557 557 readline(-1) -> (23) host: localhost:$HGPORT\r\n
558 558 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
559 559 readline(-1) -> (2) \r\n
560 560 write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
561 561 write(23 from 23) -> (321) Server: badhttpserver\r\n
562 562 write(37 from 37) -> (284) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
563 563 write(41 from 41) -> (243) Content-Type: application/mercurial-0.1\r\n
564 564 write(20 from 20) -> (223) Content-Length: 42\r\n
565 565 write(2 from 2) -> (221) \r\n
566 566 write(42 from 42) -> (179) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
567 567 readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
568 568 readline(-1) -> (27) Accept-Encoding: identity\r\n
569 569 readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
570 570 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
571 571 readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
572 572 readline(-1) -> (35) accept: application/mercurial-0.1\r\n
573 573 readline(-1) -> (23) host: localhost:$HGPORT\r\n
574 574 readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
575 575 readline(-1) -> (2) \r\n
576 576 write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
577 577 write(23 from 23) -> (120) Server: badhttpserver\r\n
578 578 write(37 from 37) -> (83) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
579 579 write(41 from 41) -> (42) Content-Type: application/mercurial-0.2\r\n
580 580 write(28 from 28) -> (14) Transfer-Encoding: chunked\r\n
581 581 write(2 from 2) -> (12) \r\n
582 582 write(6 from 6) -> (6) 1\\r\\n\x04\\r\\n (esc)
583 583 write(6 from 9) -> (0) 4\r\nnon
584 584 write limit reached; closing socket
585 585 write(27) -> 15\r\nInternal Server Error\r\n
586 586
587 587 $ rm -f error.log
588 588
589 589 Server sends partial bundle2 header magic
590 590
591 591 $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
592 592 $ cat hg.pid > $DAEMON_PIDS
593 593
594 594 $ hg clone http://localhost:$HGPORT/ clone
595 595 requesting all changes
596 abort: connection ended unexpectedly
596 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
597 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
597 598 [255]
598 599
599 600 $ killdaemons.py $DAEMON_PIDS
600 601
601 602 $ tail -7 error.log
602 603 write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n
603 604 write(2 from 2) -> (21) \r\n
604 605 write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc)
605 606 write(9 from 9) -> (6) 4\r\nnone\r\n
606 607 write(6 from 9) -> (0) 4\r\nHG2
607 608 write limit reached; closing socket
608 609 write(27) -> 15\r\nInternal Server Error\r\n
609 610
610 611 $ rm -f error.log
611 612
612 613 Server sends incomplete bundle2 stream params length
613 614
614 615 $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
615 616 $ cat hg.pid > $DAEMON_PIDS
616 617
617 618 $ hg clone http://localhost:$HGPORT/ clone
618 619 requesting all changes
619 abort: connection ended unexpectedly
620 abort: HTTP request error (incomplete response; expected 1 bytes got 3)
621 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
620 622 [255]
621 623
622 624 $ killdaemons.py $DAEMON_PIDS
623 625
624 626 $ tail -8 error.log
625 627 write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n
626 628 write(2 from 2) -> (30) \r\n
627 629 write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc)
628 630 write(9 from 9) -> (15) 4\r\nnone\r\n
629 631 write(9 from 9) -> (6) 4\r\nHG20\r\n
630 632 write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc)
631 633 write limit reached; closing socket
632 634 write(27) -> 15\r\nInternal Server Error\r\n
633 635
634 636 $ rm -f error.log
635 637
636 638 Servers stops after bundle2 stream params header
637 639
638 640 $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
639 641 $ cat hg.pid > $DAEMON_PIDS
640 642
641 643 $ hg clone http://localhost:$HGPORT/ clone
642 644 requesting all changes
643 abort: connection ended unexpectedly
645 abort: HTTP request error (incomplete response)
646 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
644 647 [255]
645 648
646 649 $ killdaemons.py $DAEMON_PIDS
647 650
648 651 $ tail -8 error.log
649 652 write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n
650 653 write(2 from 2) -> (33) \r\n
651 654 write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc)
652 655 write(9 from 9) -> (18) 4\r\nnone\r\n
653 656 write(9 from 9) -> (9) 4\r\nHG20\r\n
654 657 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
655 658 write limit reached; closing socket
656 659 write(27) -> 15\r\nInternal Server Error\r\n
657 660
658 661 $ rm -f error.log
659 662
660 663 Server stops sending after bundle2 part header length
661 664
662 665 $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
663 666 $ cat hg.pid > $DAEMON_PIDS
664 667
665 668 $ hg clone http://localhost:$HGPORT/ clone
666 669 requesting all changes
667 abort: connection ended unexpectedly
670 abort: HTTP request error (incomplete response)
671 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
668 672 [255]
669 673
670 674 $ killdaemons.py $DAEMON_PIDS
671 675
672 676 $ tail -9 error.log
673 677 write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n
674 678 write(2 from 2) -> (42) \r\n
675 679 write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc)
676 680 write(9 from 9) -> (27) 4\r\nnone\r\n
677 681 write(9 from 9) -> (18) 4\r\nHG20\r\n
678 682 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
679 683 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
680 684 write limit reached; closing socket
681 685 write(27) -> 15\r\nInternal Server Error\r\n
682 686
683 687 $ rm -f error.log
684 688
685 689 Server stops sending after bundle2 part header
686 690
687 691 $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
688 692 $ cat hg.pid > $DAEMON_PIDS
689 693
690 694 $ hg clone http://localhost:$HGPORT/ clone
691 695 requesting all changes
692 696 adding changesets
693 697 transaction abort!
694 698 rollback completed
695 699 abort: stream ended unexpectedly (got 0 bytes, expected 4)
696 700 [255]
697 701
698 702 $ killdaemons.py $DAEMON_PIDS
699 703
700 704 $ tail -10 error.log
701 705 write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n
702 706 write(2 from 2) -> (89) \r\n
703 707 write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc)
704 708 write(9 from 9) -> (74) 4\r\nnone\r\n
705 709 write(9 from 9) -> (65) 4\r\nHG20\r\n
706 710 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
707 711 write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
708 712 write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
709 713 write limit reached; closing socket
710 714 write(27) -> 15\r\nInternal Server Error\r\n
711 715
712 716 $ rm -f error.log
713 717
714 718 Server stops after bundle2 part payload chunk size
715 719
716 720 $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
717 721 $ cat hg.pid > $DAEMON_PIDS
718 722
719 723 $ hg clone http://localhost:$HGPORT/ clone
720 724 requesting all changes
721 725 adding changesets
722 726 transaction abort!
723 727 rollback completed
724 728 abort: stream ended unexpectedly (got 0 bytes, expected 4)
725 729 [255]
726 730
727 731 $ killdaemons.py $DAEMON_PIDS
728 732
729 733 $ tail -11 error.log
730 734 write(28 from 28) -> (100) Transfer-Encoding: chunked\r\n
731 735 write(2 from 2) -> (98) \r\n
732 736 write(6 from 6) -> (92) 1\\r\\n\x04\\r\\n (esc)
733 737 write(9 from 9) -> (83) 4\r\nnone\r\n
734 738 write(9 from 9) -> (74) 4\r\nHG20\r\n
735 739 write(9 from 9) -> (65) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
736 740 write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
737 741 write(47 from 47) -> (9) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
738 742 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
739 743 write limit reached; closing socket
740 744 write(27) -> 15\r\nInternal Server Error\r\n
741 745
742 746 $ rm -f error.log
743 747
744 748 Server stops sending in middle of bundle2 payload chunk
745 749
746 750 $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
747 751 $ cat hg.pid > $DAEMON_PIDS
748 752
749 753 $ hg clone http://localhost:$HGPORT/ clone
750 754 requesting all changes
751 755 adding changesets
752 756 transaction abort!
753 757 rollback completed
754 758 abort: stream ended unexpectedly (got 0 bytes, expected 4)
755 759 [255]
756 760
757 761 $ killdaemons.py $DAEMON_PIDS
758 762
759 763 $ tail -12 error.log
760 764 write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n
761 765 write(2 from 2) -> (571) \r\n
762 766 write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc)
763 767 write(9 from 9) -> (556) 4\r\nnone\r\n
764 768 write(9 from 9) -> (547) 4\r\nHG20\r\n
765 769 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
766 770 write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
767 771 write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
768 772 write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
769 773 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)
770 774 write limit reached; closing socket
771 775 write(27) -> 15\r\nInternal Server Error\r\n
772 776
773 777 $ rm -f error.log
774 778
775 779 Server stops sending after 0 length payload chunk size
776 780
777 781 $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
778 782 $ cat hg.pid > $DAEMON_PIDS
779 783
780 784 $ hg clone http://localhost:$HGPORT/ clone
781 785 requesting all changes
782 786 adding changesets
783 787 adding manifests
784 788 adding file changes
785 789 added 1 changesets with 1 changes to 1 files
786 790 transaction abort!
787 791 rollback completed
788 abort: connection ended unexpectedly
792 abort: HTTP request error (incomplete response)
793 (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
789 794 [255]
790 795
791 796 $ killdaemons.py $DAEMON_PIDS
792 797
793 798 $ tail -13 error.log
794 799 write(28 from 28) -> (582) Transfer-Encoding: chunked\r\n
795 800 write(2 from 2) -> (580) \r\n
796 801 write(6 from 6) -> (574) 1\\r\\n\x04\\r\\n (esc)
797 802 write(9 from 9) -> (565) 4\r\nnone\r\n
798 803 write(9 from 9) -> (556) 4\r\nHG20\r\n
799 804 write(9 from 9) -> (547) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
800 805 write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
801 806 write(47 from 47) -> (491) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
802 807 write(9 from 9) -> (482) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
803 808 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)
804 809 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
805 810 write limit reached; closing socket
806 811 write(27) -> 15\r\nInternal Server Error\r\n
807 812
808 813 $ rm -f error.log
809 814
810 815 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
811 816 This is before the 0 size chunked transfer part that signals end of HTTP response.
812 817
813 818 $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
814 819 $ cat hg.pid > $DAEMON_PIDS
815 820
816 821 $ hg clone http://localhost:$HGPORT/ clone
817 822 requesting all changes
818 823 adding changesets
819 824 adding manifests
820 825 adding file changes
821 826 added 1 changesets with 1 changes to 1 files
822 827 updating to branch default
823 828 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
824 829
825 830 $ killdaemons.py $DAEMON_PIDS
826 831
827 832 $ tail -22 error.log
828 833 write(28 from 28) -> (779) Transfer-Encoding: chunked\r\n
829 834 write(2 from 2) -> (777) \r\n
830 835 write(6 from 6) -> (771) 1\\r\\n\x04\\r\\n (esc)
831 836 write(9 from 9) -> (762) 4\r\nnone\r\n
832 837 write(9 from 9) -> (753) 4\r\nHG20\r\n
833 838 write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
834 839 write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
835 840 write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
836 841 write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
837 842 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)
838 843 write(9 from 9) -> (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
839 844 write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
840 845 write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
841 846 write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
842 847 write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
843 848 write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
844 849 write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
845 850 write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
846 851 write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
847 852 write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
848 853 write limit reached; closing socket
849 854 write(27) -> 15\r\nInternal Server Error\r\n
850 855
851 856 $ rm -f error.log
852 857 $ rm -rf clone
853 858
854 859 Server sends a size 0 chunked-transfer size without terminating \r\n
855 860
856 861 $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
857 862 $ cat hg.pid > $DAEMON_PIDS
858 863
859 864 $ hg clone http://localhost:$HGPORT/ clone
860 865 requesting all changes
861 866 adding changesets
862 867 adding manifests
863 868 adding file changes
864 869 added 1 changesets with 1 changes to 1 files
865 870 updating to branch default
866 871 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
867 872
868 873 $ killdaemons.py $DAEMON_PIDS
869 874
870 875 $ tail -23 error.log
871 876 write(28 from 28) -> (782) Transfer-Encoding: chunked\r\n
872 877 write(2 from 2) -> (780) \r\n
873 878 write(6 from 6) -> (774) 1\\r\\n\x04\\r\\n (esc)
874 879 write(9 from 9) -> (765) 4\r\nnone\r\n
875 880 write(9 from 9) -> (756) 4\r\nHG20\r\n
876 881 write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
877 882 write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
878 883 write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc)
879 884 write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
880 885 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)
881 886 write(9 from 9) -> (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
882 887 write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
883 888 write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc)
884 889 write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
885 890 write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n
886 891 write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
887 892 write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
888 893 write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc)
889 894 write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
890 895 write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
891 896 write(3 from 5) -> (0) 0\r\n
892 897 write limit reached; closing socket
893 898 write(27) -> 15\r\nInternal Server Error\r\n
894 899
895 900 $ rm -f error.log
896 901 $ rm -rf clone
General Comments 0
You need to be logged in to leave comments. Login now