##// END OF EJS Templates
peer-request: include more details about batch commands...
Boris Feld -
r36963:4901d1e2 default
parent child Browse files
Show More
@@ -1,1120 +1,1127 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import hashlib
10 import hashlib
11 import os
11 import os
12 import tempfile
12 import tempfile
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 nullid,
18 nullid,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup as changegroupmod,
23 changegroup as changegroupmod,
24 discovery,
24 discovery,
25 encoding,
25 encoding,
26 error,
26 error,
27 exchange,
27 exchange,
28 peer,
28 peer,
29 pushkey as pushkeymod,
29 pushkey as pushkeymod,
30 pycompat,
30 pycompat,
31 repository,
31 repository,
32 streamclone,
32 streamclone,
33 util,
33 util,
34 wireprototypes,
34 wireprototypes,
35 )
35 )
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 bytesresponse = wireprototypes.bytesresponse
40 bytesresponse = wireprototypes.bytesresponse
41 ooberror = wireprototypes.ooberror
41 ooberror = wireprototypes.ooberror
42 pushres = wireprototypes.pushres
42 pushres = wireprototypes.pushres
43 pusherr = wireprototypes.pusherr
43 pusherr = wireprototypes.pusherr
44 streamres = wireprototypes.streamres
44 streamres = wireprototypes.streamres
45 streamres_legacy = wireprototypes.streamreslegacy
45 streamres_legacy = wireprototypes.streamreslegacy
46
46
47 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
47 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
48 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
48 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
49 'IncompatibleClient')
49 'IncompatibleClient')
50 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
50 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
51
51
52 class remoteiterbatcher(peer.iterbatcher):
52 class remoteiterbatcher(peer.iterbatcher):
53 def __init__(self, remote):
53 def __init__(self, remote):
54 super(remoteiterbatcher, self).__init__()
54 super(remoteiterbatcher, self).__init__()
55 self._remote = remote
55 self._remote = remote
56
56
57 def __getattr__(self, name):
57 def __getattr__(self, name):
58 # Validate this method is batchable, since submit() only supports
58 # Validate this method is batchable, since submit() only supports
59 # batchable methods.
59 # batchable methods.
60 fn = getattr(self._remote, name)
60 fn = getattr(self._remote, name)
61 if not getattr(fn, 'batchable', None):
61 if not getattr(fn, 'batchable', None):
62 raise error.ProgrammingError('Attempted to batch a non-batchable '
62 raise error.ProgrammingError('Attempted to batch a non-batchable '
63 'call to %r' % name)
63 'call to %r' % name)
64
64
65 return super(remoteiterbatcher, self).__getattr__(name)
65 return super(remoteiterbatcher, self).__getattr__(name)
66
66
67 def submit(self):
67 def submit(self):
68 """Break the batch request into many patch calls and pipeline them.
68 """Break the batch request into many patch calls and pipeline them.
69
69
70 This is mostly valuable over http where request sizes can be
70 This is mostly valuable over http where request sizes can be
71 limited, but can be used in other places as well.
71 limited, but can be used in other places as well.
72 """
72 """
73 # 2-tuple of (command, arguments) that represents what will be
73 # 2-tuple of (command, arguments) that represents what will be
74 # sent over the wire.
74 # sent over the wire.
75 requests = []
75 requests = []
76
76
77 # 4-tuple of (command, final future, @batchable generator, remote
77 # 4-tuple of (command, final future, @batchable generator, remote
78 # future).
78 # future).
79 results = []
79 results = []
80
80
81 for command, args, opts, finalfuture in self.calls:
81 for command, args, opts, finalfuture in self.calls:
82 mtd = getattr(self._remote, command)
82 mtd = getattr(self._remote, command)
83 batchable = mtd.batchable(mtd.__self__, *args, **opts)
83 batchable = mtd.batchable(mtd.__self__, *args, **opts)
84
84
85 commandargs, fremote = next(batchable)
85 commandargs, fremote = next(batchable)
86 assert fremote
86 assert fremote
87 requests.append((command, commandargs))
87 requests.append((command, commandargs))
88 results.append((command, finalfuture, batchable, fremote))
88 results.append((command, finalfuture, batchable, fremote))
89
89
90 if requests:
90 if requests:
91 self._resultiter = self._remote._submitbatch(requests)
91 self._resultiter = self._remote._submitbatch(requests)
92
92
93 self._results = results
93 self._results = results
94
94
95 def results(self):
95 def results(self):
96 for command, finalfuture, batchable, remotefuture in self._results:
96 for command, finalfuture, batchable, remotefuture in self._results:
97 # Get the raw result, set it in the remote future, feed it
97 # Get the raw result, set it in the remote future, feed it
98 # back into the @batchable generator so it can be decoded, and
98 # back into the @batchable generator so it can be decoded, and
99 # set the result on the final future to this value.
99 # set the result on the final future to this value.
100 remoteresult = next(self._resultiter)
100 remoteresult = next(self._resultiter)
101 remotefuture.set(remoteresult)
101 remotefuture.set(remoteresult)
102 finalfuture.set(next(batchable))
102 finalfuture.set(next(batchable))
103
103
104 # Verify our @batchable generators only emit 2 values.
104 # Verify our @batchable generators only emit 2 values.
105 try:
105 try:
106 next(batchable)
106 next(batchable)
107 except StopIteration:
107 except StopIteration:
108 pass
108 pass
109 else:
109 else:
110 raise error.ProgrammingError('%s @batchable generator emitted '
110 raise error.ProgrammingError('%s @batchable generator emitted '
111 'unexpected value count' % command)
111 'unexpected value count' % command)
112
112
113 yield finalfuture.value
113 yield finalfuture.value
114
114
115 # Forward a couple of names from peer to make wireproto interactions
115 # Forward a couple of names from peer to make wireproto interactions
116 # slightly more sensible.
116 # slightly more sensible.
117 batchable = peer.batchable
117 batchable = peer.batchable
118 future = peer.future
118 future = peer.future
119
119
120 # list of nodes encoding / decoding
120 # list of nodes encoding / decoding
121
121
122 def decodelist(l, sep=' '):
122 def decodelist(l, sep=' '):
123 if l:
123 if l:
124 return [bin(v) for v in l.split(sep)]
124 return [bin(v) for v in l.split(sep)]
125 return []
125 return []
126
126
127 def encodelist(l, sep=' '):
127 def encodelist(l, sep=' '):
128 try:
128 try:
129 return sep.join(map(hex, l))
129 return sep.join(map(hex, l))
130 except TypeError:
130 except TypeError:
131 raise
131 raise
132
132
133 # batched call argument encoding
133 # batched call argument encoding
134
134
135 def escapearg(plain):
135 def escapearg(plain):
136 return (plain
136 return (plain
137 .replace(':', ':c')
137 .replace(':', ':c')
138 .replace(',', ':o')
138 .replace(',', ':o')
139 .replace(';', ':s')
139 .replace(';', ':s')
140 .replace('=', ':e'))
140 .replace('=', ':e'))
141
141
142 def unescapearg(escaped):
142 def unescapearg(escaped):
143 return (escaped
143 return (escaped
144 .replace(':e', '=')
144 .replace(':e', '=')
145 .replace(':s', ';')
145 .replace(':s', ';')
146 .replace(':o', ',')
146 .replace(':o', ',')
147 .replace(':c', ':'))
147 .replace(':c', ':'))
148
148
149 def encodebatchcmds(req):
149 def encodebatchcmds(req):
150 """Return a ``cmds`` argument value for the ``batch`` command."""
150 """Return a ``cmds`` argument value for the ``batch`` command."""
151 cmds = []
151 cmds = []
152 for op, argsdict in req:
152 for op, argsdict in req:
153 # Old servers didn't properly unescape argument names. So prevent
153 # Old servers didn't properly unescape argument names. So prevent
154 # the sending of argument names that may not be decoded properly by
154 # the sending of argument names that may not be decoded properly by
155 # servers.
155 # servers.
156 assert all(escapearg(k) == k for k in argsdict)
156 assert all(escapearg(k) == k for k in argsdict)
157
157
158 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
158 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
159 for k, v in argsdict.iteritems())
159 for k, v in argsdict.iteritems())
160 cmds.append('%s %s' % (op, args))
160 cmds.append('%s %s' % (op, args))
161
161
162 return ';'.join(cmds)
162 return ';'.join(cmds)
163
163
164 # mapping of options accepted by getbundle and their types
164 # mapping of options accepted by getbundle and their types
165 #
165 #
166 # Meant to be extended by extensions. It is extensions responsibility to ensure
166 # Meant to be extended by extensions. It is extensions responsibility to ensure
167 # such options are properly processed in exchange.getbundle.
167 # such options are properly processed in exchange.getbundle.
168 #
168 #
169 # supported types are:
169 # supported types are:
170 #
170 #
171 # :nodes: list of binary nodes
171 # :nodes: list of binary nodes
172 # :csv: list of comma-separated values
172 # :csv: list of comma-separated values
173 # :scsv: list of comma-separated values return as set
173 # :scsv: list of comma-separated values return as set
174 # :plain: string with no transformation needed.
174 # :plain: string with no transformation needed.
175 gboptsmap = {'heads': 'nodes',
175 gboptsmap = {'heads': 'nodes',
176 'bookmarks': 'boolean',
176 'bookmarks': 'boolean',
177 'common': 'nodes',
177 'common': 'nodes',
178 'obsmarkers': 'boolean',
178 'obsmarkers': 'boolean',
179 'phases': 'boolean',
179 'phases': 'boolean',
180 'bundlecaps': 'scsv',
180 'bundlecaps': 'scsv',
181 'listkeys': 'csv',
181 'listkeys': 'csv',
182 'cg': 'boolean',
182 'cg': 'boolean',
183 'cbattempted': 'boolean',
183 'cbattempted': 'boolean',
184 'stream': 'boolean',
184 'stream': 'boolean',
185 }
185 }
186
186
187 # client side
187 # client side
188
188
189 class wirepeer(repository.legacypeer):
189 class wirepeer(repository.legacypeer):
190 """Client-side interface for communicating with a peer repository.
190 """Client-side interface for communicating with a peer repository.
191
191
192 Methods commonly call wire protocol commands of the same name.
192 Methods commonly call wire protocol commands of the same name.
193
193
194 See also httppeer.py and sshpeer.py for protocol-specific
194 See also httppeer.py and sshpeer.py for protocol-specific
195 implementations of this interface.
195 implementations of this interface.
196 """
196 """
197 # Begin of basewirepeer interface.
197 # Begin of basewirepeer interface.
198
198
199 def iterbatch(self):
199 def iterbatch(self):
200 return remoteiterbatcher(self)
200 return remoteiterbatcher(self)
201
201
202 @batchable
202 @batchable
203 def lookup(self, key):
203 def lookup(self, key):
204 self.requirecap('lookup', _('look up remote revision'))
204 self.requirecap('lookup', _('look up remote revision'))
205 f = future()
205 f = future()
206 yield {'key': encoding.fromlocal(key)}, f
206 yield {'key': encoding.fromlocal(key)}, f
207 d = f.value
207 d = f.value
208 success, data = d[:-1].split(" ", 1)
208 success, data = d[:-1].split(" ", 1)
209 if int(success):
209 if int(success):
210 yield bin(data)
210 yield bin(data)
211 else:
211 else:
212 self._abort(error.RepoError(data))
212 self._abort(error.RepoError(data))
213
213
214 @batchable
214 @batchable
215 def heads(self):
215 def heads(self):
216 f = future()
216 f = future()
217 yield {}, f
217 yield {}, f
218 d = f.value
218 d = f.value
219 try:
219 try:
220 yield decodelist(d[:-1])
220 yield decodelist(d[:-1])
221 except ValueError:
221 except ValueError:
222 self._abort(error.ResponseError(_("unexpected response:"), d))
222 self._abort(error.ResponseError(_("unexpected response:"), d))
223
223
224 @batchable
224 @batchable
225 def known(self, nodes):
225 def known(self, nodes):
226 f = future()
226 f = future()
227 yield {'nodes': encodelist(nodes)}, f
227 yield {'nodes': encodelist(nodes)}, f
228 d = f.value
228 d = f.value
229 try:
229 try:
230 yield [bool(int(b)) for b in d]
230 yield [bool(int(b)) for b in d]
231 except ValueError:
231 except ValueError:
232 self._abort(error.ResponseError(_("unexpected response:"), d))
232 self._abort(error.ResponseError(_("unexpected response:"), d))
233
233
234 @batchable
234 @batchable
235 def branchmap(self):
235 def branchmap(self):
236 f = future()
236 f = future()
237 yield {}, f
237 yield {}, f
238 d = f.value
238 d = f.value
239 try:
239 try:
240 branchmap = {}
240 branchmap = {}
241 for branchpart in d.splitlines():
241 for branchpart in d.splitlines():
242 branchname, branchheads = branchpart.split(' ', 1)
242 branchname, branchheads = branchpart.split(' ', 1)
243 branchname = encoding.tolocal(urlreq.unquote(branchname))
243 branchname = encoding.tolocal(urlreq.unquote(branchname))
244 branchheads = decodelist(branchheads)
244 branchheads = decodelist(branchheads)
245 branchmap[branchname] = branchheads
245 branchmap[branchname] = branchheads
246 yield branchmap
246 yield branchmap
247 except TypeError:
247 except TypeError:
248 self._abort(error.ResponseError(_("unexpected response:"), d))
248 self._abort(error.ResponseError(_("unexpected response:"), d))
249
249
250 @batchable
250 @batchable
251 def listkeys(self, namespace):
251 def listkeys(self, namespace):
252 if not self.capable('pushkey'):
252 if not self.capable('pushkey'):
253 yield {}, None
253 yield {}, None
254 f = future()
254 f = future()
255 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
255 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
256 yield {'namespace': encoding.fromlocal(namespace)}, f
256 yield {'namespace': encoding.fromlocal(namespace)}, f
257 d = f.value
257 d = f.value
258 self.ui.debug('received listkey for "%s": %i bytes\n'
258 self.ui.debug('received listkey for "%s": %i bytes\n'
259 % (namespace, len(d)))
259 % (namespace, len(d)))
260 yield pushkeymod.decodekeys(d)
260 yield pushkeymod.decodekeys(d)
261
261
262 @batchable
262 @batchable
263 def pushkey(self, namespace, key, old, new):
263 def pushkey(self, namespace, key, old, new):
264 if not self.capable('pushkey'):
264 if not self.capable('pushkey'):
265 yield False, None
265 yield False, None
266 f = future()
266 f = future()
267 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
267 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
268 yield {'namespace': encoding.fromlocal(namespace),
268 yield {'namespace': encoding.fromlocal(namespace),
269 'key': encoding.fromlocal(key),
269 'key': encoding.fromlocal(key),
270 'old': encoding.fromlocal(old),
270 'old': encoding.fromlocal(old),
271 'new': encoding.fromlocal(new)}, f
271 'new': encoding.fromlocal(new)}, f
272 d = f.value
272 d = f.value
273 d, output = d.split('\n', 1)
273 d, output = d.split('\n', 1)
274 try:
274 try:
275 d = bool(int(d))
275 d = bool(int(d))
276 except ValueError:
276 except ValueError:
277 raise error.ResponseError(
277 raise error.ResponseError(
278 _('push failed (unexpected response):'), d)
278 _('push failed (unexpected response):'), d)
279 for l in output.splitlines(True):
279 for l in output.splitlines(True):
280 self.ui.status(_('remote: '), l)
280 self.ui.status(_('remote: '), l)
281 yield d
281 yield d
282
282
283 def stream_out(self):
283 def stream_out(self):
284 return self._callstream('stream_out')
284 return self._callstream('stream_out')
285
285
286 def getbundle(self, source, **kwargs):
286 def getbundle(self, source, **kwargs):
287 kwargs = pycompat.byteskwargs(kwargs)
287 kwargs = pycompat.byteskwargs(kwargs)
288 self.requirecap('getbundle', _('look up remote changes'))
288 self.requirecap('getbundle', _('look up remote changes'))
289 opts = {}
289 opts = {}
290 bundlecaps = kwargs.get('bundlecaps')
290 bundlecaps = kwargs.get('bundlecaps')
291 if bundlecaps is not None:
291 if bundlecaps is not None:
292 kwargs['bundlecaps'] = sorted(bundlecaps)
292 kwargs['bundlecaps'] = sorted(bundlecaps)
293 else:
293 else:
294 bundlecaps = () # kwargs could have it to None
294 bundlecaps = () # kwargs could have it to None
295 for key, value in kwargs.iteritems():
295 for key, value in kwargs.iteritems():
296 if value is None:
296 if value is None:
297 continue
297 continue
298 keytype = gboptsmap.get(key)
298 keytype = gboptsmap.get(key)
299 if keytype is None:
299 if keytype is None:
300 raise error.ProgrammingError(
300 raise error.ProgrammingError(
301 'Unexpectedly None keytype for key %s' % key)
301 'Unexpectedly None keytype for key %s' % key)
302 elif keytype == 'nodes':
302 elif keytype == 'nodes':
303 value = encodelist(value)
303 value = encodelist(value)
304 elif keytype in ('csv', 'scsv'):
304 elif keytype in ('csv', 'scsv'):
305 value = ','.join(value)
305 value = ','.join(value)
306 elif keytype == 'boolean':
306 elif keytype == 'boolean':
307 value = '%i' % bool(value)
307 value = '%i' % bool(value)
308 elif keytype != 'plain':
308 elif keytype != 'plain':
309 raise KeyError('unknown getbundle option type %s'
309 raise KeyError('unknown getbundle option type %s'
310 % keytype)
310 % keytype)
311 opts[key] = value
311 opts[key] = value
312 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
312 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
313 if any((cap.startswith('HG2') for cap in bundlecaps)):
313 if any((cap.startswith('HG2') for cap in bundlecaps)):
314 return bundle2.getunbundler(self.ui, f)
314 return bundle2.getunbundler(self.ui, f)
315 else:
315 else:
316 return changegroupmod.cg1unpacker(f, 'UN')
316 return changegroupmod.cg1unpacker(f, 'UN')
317
317
318 def unbundle(self, cg, heads, url):
318 def unbundle(self, cg, heads, url):
319 '''Send cg (a readable file-like object representing the
319 '''Send cg (a readable file-like object representing the
320 changegroup to push, typically a chunkbuffer object) to the
320 changegroup to push, typically a chunkbuffer object) to the
321 remote server as a bundle.
321 remote server as a bundle.
322
322
323 When pushing a bundle10 stream, return an integer indicating the
323 When pushing a bundle10 stream, return an integer indicating the
324 result of the push (see changegroup.apply()).
324 result of the push (see changegroup.apply()).
325
325
326 When pushing a bundle20 stream, return a bundle20 stream.
326 When pushing a bundle20 stream, return a bundle20 stream.
327
327
328 `url` is the url the client thinks it's pushing to, which is
328 `url` is the url the client thinks it's pushing to, which is
329 visible to hooks.
329 visible to hooks.
330 '''
330 '''
331
331
332 if heads != ['force'] and self.capable('unbundlehash'):
332 if heads != ['force'] and self.capable('unbundlehash'):
333 heads = encodelist(['hashed',
333 heads = encodelist(['hashed',
334 hashlib.sha1(''.join(sorted(heads))).digest()])
334 hashlib.sha1(''.join(sorted(heads))).digest()])
335 else:
335 else:
336 heads = encodelist(heads)
336 heads = encodelist(heads)
337
337
338 if util.safehasattr(cg, 'deltaheader'):
338 if util.safehasattr(cg, 'deltaheader'):
339 # this a bundle10, do the old style call sequence
339 # this a bundle10, do the old style call sequence
340 ret, output = self._callpush("unbundle", cg, heads=heads)
340 ret, output = self._callpush("unbundle", cg, heads=heads)
341 if ret == "":
341 if ret == "":
342 raise error.ResponseError(
342 raise error.ResponseError(
343 _('push failed:'), output)
343 _('push failed:'), output)
344 try:
344 try:
345 ret = int(ret)
345 ret = int(ret)
346 except ValueError:
346 except ValueError:
347 raise error.ResponseError(
347 raise error.ResponseError(
348 _('push failed (unexpected response):'), ret)
348 _('push failed (unexpected response):'), ret)
349
349
350 for l in output.splitlines(True):
350 for l in output.splitlines(True):
351 self.ui.status(_('remote: '), l)
351 self.ui.status(_('remote: '), l)
352 else:
352 else:
353 # bundle2 push. Send a stream, fetch a stream.
353 # bundle2 push. Send a stream, fetch a stream.
354 stream = self._calltwowaystream('unbundle', cg, heads=heads)
354 stream = self._calltwowaystream('unbundle', cg, heads=heads)
355 ret = bundle2.getunbundler(self.ui, stream)
355 ret = bundle2.getunbundler(self.ui, stream)
356 return ret
356 return ret
357
357
358 # End of basewirepeer interface.
358 # End of basewirepeer interface.
359
359
360 # Begin of baselegacywirepeer interface.
360 # Begin of baselegacywirepeer interface.
361
361
362 def branches(self, nodes):
362 def branches(self, nodes):
363 n = encodelist(nodes)
363 n = encodelist(nodes)
364 d = self._call("branches", nodes=n)
364 d = self._call("branches", nodes=n)
365 try:
365 try:
366 br = [tuple(decodelist(b)) for b in d.splitlines()]
366 br = [tuple(decodelist(b)) for b in d.splitlines()]
367 return br
367 return br
368 except ValueError:
368 except ValueError:
369 self._abort(error.ResponseError(_("unexpected response:"), d))
369 self._abort(error.ResponseError(_("unexpected response:"), d))
370
370
371 def between(self, pairs):
371 def between(self, pairs):
372 batch = 8 # avoid giant requests
372 batch = 8 # avoid giant requests
373 r = []
373 r = []
374 for i in xrange(0, len(pairs), batch):
374 for i in xrange(0, len(pairs), batch):
375 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
375 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
376 d = self._call("between", pairs=n)
376 d = self._call("between", pairs=n)
377 try:
377 try:
378 r.extend(l and decodelist(l) or [] for l in d.splitlines())
378 r.extend(l and decodelist(l) or [] for l in d.splitlines())
379 except ValueError:
379 except ValueError:
380 self._abort(error.ResponseError(_("unexpected response:"), d))
380 self._abort(error.ResponseError(_("unexpected response:"), d))
381 return r
381 return r
382
382
383 def changegroup(self, nodes, kind):
383 def changegroup(self, nodes, kind):
384 n = encodelist(nodes)
384 n = encodelist(nodes)
385 f = self._callcompressable("changegroup", roots=n)
385 f = self._callcompressable("changegroup", roots=n)
386 return changegroupmod.cg1unpacker(f, 'UN')
386 return changegroupmod.cg1unpacker(f, 'UN')
387
387
388 def changegroupsubset(self, bases, heads, kind):
388 def changegroupsubset(self, bases, heads, kind):
389 self.requirecap('changegroupsubset', _('look up remote changes'))
389 self.requirecap('changegroupsubset', _('look up remote changes'))
390 bases = encodelist(bases)
390 bases = encodelist(bases)
391 heads = encodelist(heads)
391 heads = encodelist(heads)
392 f = self._callcompressable("changegroupsubset",
392 f = self._callcompressable("changegroupsubset",
393 bases=bases, heads=heads)
393 bases=bases, heads=heads)
394 return changegroupmod.cg1unpacker(f, 'UN')
394 return changegroupmod.cg1unpacker(f, 'UN')
395
395
396 # End of baselegacywirepeer interface.
396 # End of baselegacywirepeer interface.
397
397
398 def _submitbatch(self, req):
398 def _submitbatch(self, req):
399 """run batch request <req> on the server
399 """run batch request <req> on the server
400
400
401 Returns an iterator of the raw responses from the server.
401 Returns an iterator of the raw responses from the server.
402 """
402 """
403 ui = self.ui
404 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
405 ui.debug('devel-peer-request: batched-content\n')
406 for op, args in req:
407 msg = 'devel-peer-request: - %s (%d arguments)\n'
408 ui.debug(msg % (op, len(args)))
409
403 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
410 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
404 chunk = rsp.read(1024)
411 chunk = rsp.read(1024)
405 work = [chunk]
412 work = [chunk]
406 while chunk:
413 while chunk:
407 while ';' not in chunk and chunk:
414 while ';' not in chunk and chunk:
408 chunk = rsp.read(1024)
415 chunk = rsp.read(1024)
409 work.append(chunk)
416 work.append(chunk)
410 merged = ''.join(work)
417 merged = ''.join(work)
411 while ';' in merged:
418 while ';' in merged:
412 one, merged = merged.split(';', 1)
419 one, merged = merged.split(';', 1)
413 yield unescapearg(one)
420 yield unescapearg(one)
414 chunk = rsp.read(1024)
421 chunk = rsp.read(1024)
415 work = [merged, chunk]
422 work = [merged, chunk]
416 yield unescapearg(''.join(work))
423 yield unescapearg(''.join(work))
417
424
418 def _submitone(self, op, args):
425 def _submitone(self, op, args):
419 return self._call(op, **pycompat.strkwargs(args))
426 return self._call(op, **pycompat.strkwargs(args))
420
427
421 def debugwireargs(self, one, two, three=None, four=None, five=None):
428 def debugwireargs(self, one, two, three=None, four=None, five=None):
422 # don't pass optional arguments left at their default value
429 # don't pass optional arguments left at their default value
423 opts = {}
430 opts = {}
424 if three is not None:
431 if three is not None:
425 opts[r'three'] = three
432 opts[r'three'] = three
426 if four is not None:
433 if four is not None:
427 opts[r'four'] = four
434 opts[r'four'] = four
428 return self._call('debugwireargs', one=one, two=two, **opts)
435 return self._call('debugwireargs', one=one, two=two, **opts)
429
436
430 def _call(self, cmd, **args):
437 def _call(self, cmd, **args):
431 """execute <cmd> on the server
438 """execute <cmd> on the server
432
439
433 The command is expected to return a simple string.
440 The command is expected to return a simple string.
434
441
435 returns the server reply as a string."""
442 returns the server reply as a string."""
436 raise NotImplementedError()
443 raise NotImplementedError()
437
444
438 def _callstream(self, cmd, **args):
445 def _callstream(self, cmd, **args):
439 """execute <cmd> on the server
446 """execute <cmd> on the server
440
447
441 The command is expected to return a stream. Note that if the
448 The command is expected to return a stream. Note that if the
442 command doesn't return a stream, _callstream behaves
449 command doesn't return a stream, _callstream behaves
443 differently for ssh and http peers.
450 differently for ssh and http peers.
444
451
445 returns the server reply as a file like object.
452 returns the server reply as a file like object.
446 """
453 """
447 raise NotImplementedError()
454 raise NotImplementedError()
448
455
449 def _callcompressable(self, cmd, **args):
456 def _callcompressable(self, cmd, **args):
450 """execute <cmd> on the server
457 """execute <cmd> on the server
451
458
452 The command is expected to return a stream.
459 The command is expected to return a stream.
453
460
454 The stream may have been compressed in some implementations. This
461 The stream may have been compressed in some implementations. This
455 function takes care of the decompression. This is the only difference
462 function takes care of the decompression. This is the only difference
456 with _callstream.
463 with _callstream.
457
464
458 returns the server reply as a file like object.
465 returns the server reply as a file like object.
459 """
466 """
460 raise NotImplementedError()
467 raise NotImplementedError()
461
468
462 def _callpush(self, cmd, fp, **args):
469 def _callpush(self, cmd, fp, **args):
463 """execute a <cmd> on server
470 """execute a <cmd> on server
464
471
465 The command is expected to be related to a push. Push has a special
472 The command is expected to be related to a push. Push has a special
466 return method.
473 return method.
467
474
468 returns the server reply as a (ret, output) tuple. ret is either
475 returns the server reply as a (ret, output) tuple. ret is either
469 empty (error) or a stringified int.
476 empty (error) or a stringified int.
470 """
477 """
471 raise NotImplementedError()
478 raise NotImplementedError()
472
479
473 def _calltwowaystream(self, cmd, fp, **args):
480 def _calltwowaystream(self, cmd, fp, **args):
474 """execute <cmd> on server
481 """execute <cmd> on server
475
482
476 The command will send a stream to the server and get a stream in reply.
483 The command will send a stream to the server and get a stream in reply.
477 """
484 """
478 raise NotImplementedError()
485 raise NotImplementedError()
479
486
480 def _abort(self, exception):
487 def _abort(self, exception):
481 """clearly abort the wire protocol connection and raise the exception
488 """clearly abort the wire protocol connection and raise the exception
482 """
489 """
483 raise NotImplementedError()
490 raise NotImplementedError()
484
491
485 # server side
492 # server side
486
493
487 # wire protocol command can either return a string or one of these classes.
494 # wire protocol command can either return a string or one of these classes.
488
495
489 def getdispatchrepo(repo, proto, command):
496 def getdispatchrepo(repo, proto, command):
490 """Obtain the repo used for processing wire protocol commands.
497 """Obtain the repo used for processing wire protocol commands.
491
498
492 The intent of this function is to serve as a monkeypatch point for
499 The intent of this function is to serve as a monkeypatch point for
493 extensions that need commands to operate on different repo views under
500 extensions that need commands to operate on different repo views under
494 specialized circumstances.
501 specialized circumstances.
495 """
502 """
496 return repo.filtered('served')
503 return repo.filtered('served')
497
504
498 def dispatch(repo, proto, command):
505 def dispatch(repo, proto, command):
499 repo = getdispatchrepo(repo, proto, command)
506 repo = getdispatchrepo(repo, proto, command)
500 func, spec = commands[command]
507 func, spec = commands[command]
501 args = proto.getargs(spec)
508 args = proto.getargs(spec)
502 return func(repo, proto, *args)
509 return func(repo, proto, *args)
503
510
504 def options(cmd, keys, others):
511 def options(cmd, keys, others):
505 opts = {}
512 opts = {}
506 for k in keys:
513 for k in keys:
507 if k in others:
514 if k in others:
508 opts[k] = others[k]
515 opts[k] = others[k]
509 del others[k]
516 del others[k]
510 if others:
517 if others:
511 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
518 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
512 % (cmd, ",".join(others)))
519 % (cmd, ",".join(others)))
513 return opts
520 return opts
514
521
515 def bundle1allowed(repo, action):
522 def bundle1allowed(repo, action):
516 """Whether a bundle1 operation is allowed from the server.
523 """Whether a bundle1 operation is allowed from the server.
517
524
518 Priority is:
525 Priority is:
519
526
520 1. server.bundle1gd.<action> (if generaldelta active)
527 1. server.bundle1gd.<action> (if generaldelta active)
521 2. server.bundle1.<action>
528 2. server.bundle1.<action>
522 3. server.bundle1gd (if generaldelta active)
529 3. server.bundle1gd (if generaldelta active)
523 4. server.bundle1
530 4. server.bundle1
524 """
531 """
525 ui = repo.ui
532 ui = repo.ui
526 gd = 'generaldelta' in repo.requirements
533 gd = 'generaldelta' in repo.requirements
527
534
528 if gd:
535 if gd:
529 v = ui.configbool('server', 'bundle1gd.%s' % action)
536 v = ui.configbool('server', 'bundle1gd.%s' % action)
530 if v is not None:
537 if v is not None:
531 return v
538 return v
532
539
533 v = ui.configbool('server', 'bundle1.%s' % action)
540 v = ui.configbool('server', 'bundle1.%s' % action)
534 if v is not None:
541 if v is not None:
535 return v
542 return v
536
543
537 if gd:
544 if gd:
538 v = ui.configbool('server', 'bundle1gd')
545 v = ui.configbool('server', 'bundle1gd')
539 if v is not None:
546 if v is not None:
540 return v
547 return v
541
548
542 return ui.configbool('server', 'bundle1')
549 return ui.configbool('server', 'bundle1')
543
550
544 def supportedcompengines(ui, role):
551 def supportedcompengines(ui, role):
545 """Obtain the list of supported compression engines for a request."""
552 """Obtain the list of supported compression engines for a request."""
546 assert role in (util.CLIENTROLE, util.SERVERROLE)
553 assert role in (util.CLIENTROLE, util.SERVERROLE)
547
554
548 compengines = util.compengines.supportedwireengines(role)
555 compengines = util.compengines.supportedwireengines(role)
549
556
550 # Allow config to override default list and ordering.
557 # Allow config to override default list and ordering.
551 if role == util.SERVERROLE:
558 if role == util.SERVERROLE:
552 configengines = ui.configlist('server', 'compressionengines')
559 configengines = ui.configlist('server', 'compressionengines')
553 config = 'server.compressionengines'
560 config = 'server.compressionengines'
554 else:
561 else:
555 # This is currently implemented mainly to facilitate testing. In most
562 # This is currently implemented mainly to facilitate testing. In most
556 # cases, the server should be in charge of choosing a compression engine
563 # cases, the server should be in charge of choosing a compression engine
557 # because a server has the most to lose from a sub-optimal choice. (e.g.
564 # because a server has the most to lose from a sub-optimal choice. (e.g.
558 # CPU DoS due to an expensive engine or a network DoS due to poor
565 # CPU DoS due to an expensive engine or a network DoS due to poor
559 # compression ratio).
566 # compression ratio).
560 configengines = ui.configlist('experimental',
567 configengines = ui.configlist('experimental',
561 'clientcompressionengines')
568 'clientcompressionengines')
562 config = 'experimental.clientcompressionengines'
569 config = 'experimental.clientcompressionengines'
563
570
564 # No explicit config. Filter out the ones that aren't supposed to be
571 # No explicit config. Filter out the ones that aren't supposed to be
565 # advertised and return default ordering.
572 # advertised and return default ordering.
566 if not configengines:
573 if not configengines:
567 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
574 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
568 return [e for e in compengines
575 return [e for e in compengines
569 if getattr(e.wireprotosupport(), attr) > 0]
576 if getattr(e.wireprotosupport(), attr) > 0]
570
577
571 # If compression engines are listed in the config, assume there is a good
578 # If compression engines are listed in the config, assume there is a good
572 # reason for it (like server operators wanting to achieve specific
579 # reason for it (like server operators wanting to achieve specific
573 # performance characteristics). So fail fast if the config references
580 # performance characteristics). So fail fast if the config references
574 # unusable compression engines.
581 # unusable compression engines.
575 validnames = set(e.name() for e in compengines)
582 validnames = set(e.name() for e in compengines)
576 invalidnames = set(e for e in configengines if e not in validnames)
583 invalidnames = set(e for e in configengines if e not in validnames)
577 if invalidnames:
584 if invalidnames:
578 raise error.Abort(_('invalid compression engine defined in %s: %s') %
585 raise error.Abort(_('invalid compression engine defined in %s: %s') %
579 (config, ', '.join(sorted(invalidnames))))
586 (config, ', '.join(sorted(invalidnames))))
580
587
581 compengines = [e for e in compengines if e.name() in configengines]
588 compengines = [e for e in compengines if e.name() in configengines]
582 compengines = sorted(compengines,
589 compengines = sorted(compengines,
583 key=lambda e: configengines.index(e.name()))
590 key=lambda e: configengines.index(e.name()))
584
591
585 if not compengines:
592 if not compengines:
586 raise error.Abort(_('%s config option does not specify any known '
593 raise error.Abort(_('%s config option does not specify any known '
587 'compression engines') % config,
594 'compression engines') % config,
588 hint=_('usable compression engines: %s') %
595 hint=_('usable compression engines: %s') %
589 ', '.sorted(validnames))
596 ', '.sorted(validnames))
590
597
591 return compengines
598 return compengines
592
599
593 class commandentry(object):
600 class commandentry(object):
594 """Represents a declared wire protocol command."""
601 """Represents a declared wire protocol command."""
595 def __init__(self, func, args='', transports=None,
602 def __init__(self, func, args='', transports=None,
596 permission='push'):
603 permission='push'):
597 self.func = func
604 self.func = func
598 self.args = args
605 self.args = args
599 self.transports = transports or set()
606 self.transports = transports or set()
600 self.permission = permission
607 self.permission = permission
601
608
602 def _merge(self, func, args):
609 def _merge(self, func, args):
603 """Merge this instance with an incoming 2-tuple.
610 """Merge this instance with an incoming 2-tuple.
604
611
605 This is called when a caller using the old 2-tuple API attempts
612 This is called when a caller using the old 2-tuple API attempts
606 to replace an instance. The incoming values are merged with
613 to replace an instance. The incoming values are merged with
607 data not captured by the 2-tuple and a new instance containing
614 data not captured by the 2-tuple and a new instance containing
608 the union of the two objects is returned.
615 the union of the two objects is returned.
609 """
616 """
610 return commandentry(func, args=args, transports=set(self.transports),
617 return commandentry(func, args=args, transports=set(self.transports),
611 permission=self.permission)
618 permission=self.permission)
612
619
613 # Old code treats instances as 2-tuples. So expose that interface.
620 # Old code treats instances as 2-tuples. So expose that interface.
614 def __iter__(self):
621 def __iter__(self):
615 yield self.func
622 yield self.func
616 yield self.args
623 yield self.args
617
624
618 def __getitem__(self, i):
625 def __getitem__(self, i):
619 if i == 0:
626 if i == 0:
620 return self.func
627 return self.func
621 elif i == 1:
628 elif i == 1:
622 return self.args
629 return self.args
623 else:
630 else:
624 raise IndexError('can only access elements 0 and 1')
631 raise IndexError('can only access elements 0 and 1')
625
632
626 class commanddict(dict):
633 class commanddict(dict):
627 """Container for registered wire protocol commands.
634 """Container for registered wire protocol commands.
628
635
629 It behaves like a dict. But __setitem__ is overwritten to allow silent
636 It behaves like a dict. But __setitem__ is overwritten to allow silent
630 coercion of values from 2-tuples for API compatibility.
637 coercion of values from 2-tuples for API compatibility.
631 """
638 """
632 def __setitem__(self, k, v):
639 def __setitem__(self, k, v):
633 if isinstance(v, commandentry):
640 if isinstance(v, commandentry):
634 pass
641 pass
635 # Cast 2-tuples to commandentry instances.
642 # Cast 2-tuples to commandentry instances.
636 elif isinstance(v, tuple):
643 elif isinstance(v, tuple):
637 if len(v) != 2:
644 if len(v) != 2:
638 raise ValueError('command tuples must have exactly 2 elements')
645 raise ValueError('command tuples must have exactly 2 elements')
639
646
640 # It is common for extensions to wrap wire protocol commands via
647 # It is common for extensions to wrap wire protocol commands via
641 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
648 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
642 # doing this aren't aware of the new API that uses objects to store
649 # doing this aren't aware of the new API that uses objects to store
643 # command entries, we automatically merge old state with new.
650 # command entries, we automatically merge old state with new.
644 if k in self:
651 if k in self:
645 v = self[k]._merge(v[0], v[1])
652 v = self[k]._merge(v[0], v[1])
646 else:
653 else:
647 # Use default values from @wireprotocommand.
654 # Use default values from @wireprotocommand.
648 v = commandentry(v[0], args=v[1],
655 v = commandentry(v[0], args=v[1],
649 transports=set(wireprototypes.TRANSPORTS),
656 transports=set(wireprototypes.TRANSPORTS),
650 permission='push')
657 permission='push')
651 else:
658 else:
652 raise ValueError('command entries must be commandentry instances '
659 raise ValueError('command entries must be commandentry instances '
653 'or 2-tuples')
660 'or 2-tuples')
654
661
655 return super(commanddict, self).__setitem__(k, v)
662 return super(commanddict, self).__setitem__(k, v)
656
663
657 def commandavailable(self, command, proto):
664 def commandavailable(self, command, proto):
658 """Determine if a command is available for the requested protocol."""
665 """Determine if a command is available for the requested protocol."""
659 assert proto.name in wireprototypes.TRANSPORTS
666 assert proto.name in wireprototypes.TRANSPORTS
660
667
661 entry = self.get(command)
668 entry = self.get(command)
662
669
663 if not entry:
670 if not entry:
664 return False
671 return False
665
672
666 if proto.name not in entry.transports:
673 if proto.name not in entry.transports:
667 return False
674 return False
668
675
669 return True
676 return True
670
677
671 # Constants specifying which transports a wire protocol command should be
678 # Constants specifying which transports a wire protocol command should be
672 # available on. For use with @wireprotocommand.
679 # available on. For use with @wireprotocommand.
673 POLICY_ALL = 'all'
680 POLICY_ALL = 'all'
674 POLICY_V1_ONLY = 'v1-only'
681 POLICY_V1_ONLY = 'v1-only'
675 POLICY_V2_ONLY = 'v2-only'
682 POLICY_V2_ONLY = 'v2-only'
676
683
677 commands = commanddict()
684 commands = commanddict()
678
685
679 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL,
686 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL,
680 permission='push'):
687 permission='push'):
681 """Decorator to declare a wire protocol command.
688 """Decorator to declare a wire protocol command.
682
689
683 ``name`` is the name of the wire protocol command being provided.
690 ``name`` is the name of the wire protocol command being provided.
684
691
685 ``args`` is a space-delimited list of named arguments that the command
692 ``args`` is a space-delimited list of named arguments that the command
686 accepts. ``*`` is a special value that says to accept all arguments.
693 accepts. ``*`` is a special value that says to accept all arguments.
687
694
688 ``transportpolicy`` is a POLICY_* constant denoting which transports
695 ``transportpolicy`` is a POLICY_* constant denoting which transports
689 this wire protocol command should be exposed to. By default, commands
696 this wire protocol command should be exposed to. By default, commands
690 are exposed to all wire protocol transports.
697 are exposed to all wire protocol transports.
691
698
692 ``permission`` defines the permission type needed to run this command.
699 ``permission`` defines the permission type needed to run this command.
693 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
700 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
694 respectively. Default is to assume command requires ``push`` permissions
701 respectively. Default is to assume command requires ``push`` permissions
695 because otherwise commands not declaring their permissions could modify
702 because otherwise commands not declaring their permissions could modify
696 a repository that is supposed to be read-only.
703 a repository that is supposed to be read-only.
697 """
704 """
698 if transportpolicy == POLICY_ALL:
705 if transportpolicy == POLICY_ALL:
699 transports = set(wireprototypes.TRANSPORTS)
706 transports = set(wireprototypes.TRANSPORTS)
700 elif transportpolicy == POLICY_V1_ONLY:
707 elif transportpolicy == POLICY_V1_ONLY:
701 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
708 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
702 if v['version'] == 1}
709 if v['version'] == 1}
703 elif transportpolicy == POLICY_V2_ONLY:
710 elif transportpolicy == POLICY_V2_ONLY:
704 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
711 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
705 if v['version'] == 2}
712 if v['version'] == 2}
706 else:
713 else:
707 raise error.ProgrammingError('invalid transport policy value: %s' %
714 raise error.ProgrammingError('invalid transport policy value: %s' %
708 transportpolicy)
715 transportpolicy)
709
716
710 if permission not in ('push', 'pull'):
717 if permission not in ('push', 'pull'):
711 raise error.ProgrammingError('invalid wire protocol permission; '
718 raise error.ProgrammingError('invalid wire protocol permission; '
712 'got %s; expected "push" or "pull"' %
719 'got %s; expected "push" or "pull"' %
713 permission)
720 permission)
714
721
715 def register(func):
722 def register(func):
716 commands[name] = commandentry(func, args=args, transports=transports,
723 commands[name] = commandentry(func, args=args, transports=transports,
717 permission=permission)
724 permission=permission)
718 return func
725 return func
719 return register
726 return register
720
727
721 # TODO define a more appropriate permissions type to use for this.
728 # TODO define a more appropriate permissions type to use for this.
722 @wireprotocommand('batch', 'cmds *', permission='pull')
729 @wireprotocommand('batch', 'cmds *', permission='pull')
723 def batch(repo, proto, cmds, others):
730 def batch(repo, proto, cmds, others):
724 repo = repo.filtered("served")
731 repo = repo.filtered("served")
725 res = []
732 res = []
726 for pair in cmds.split(';'):
733 for pair in cmds.split(';'):
727 op, args = pair.split(' ', 1)
734 op, args = pair.split(' ', 1)
728 vals = {}
735 vals = {}
729 for a in args.split(','):
736 for a in args.split(','):
730 if a:
737 if a:
731 n, v = a.split('=')
738 n, v = a.split('=')
732 vals[unescapearg(n)] = unescapearg(v)
739 vals[unescapearg(n)] = unescapearg(v)
733 func, spec = commands[op]
740 func, spec = commands[op]
734
741
735 # Validate that client has permissions to perform this command.
742 # Validate that client has permissions to perform this command.
736 perm = commands[op].permission
743 perm = commands[op].permission
737 assert perm in ('push', 'pull')
744 assert perm in ('push', 'pull')
738 proto.checkperm(perm)
745 proto.checkperm(perm)
739
746
740 if spec:
747 if spec:
741 keys = spec.split()
748 keys = spec.split()
742 data = {}
749 data = {}
743 for k in keys:
750 for k in keys:
744 if k == '*':
751 if k == '*':
745 star = {}
752 star = {}
746 for key in vals.keys():
753 for key in vals.keys():
747 if key not in keys:
754 if key not in keys:
748 star[key] = vals[key]
755 star[key] = vals[key]
749 data['*'] = star
756 data['*'] = star
750 else:
757 else:
751 data[k] = vals[k]
758 data[k] = vals[k]
752 result = func(repo, proto, *[data[k] for k in keys])
759 result = func(repo, proto, *[data[k] for k in keys])
753 else:
760 else:
754 result = func(repo, proto)
761 result = func(repo, proto)
755 if isinstance(result, ooberror):
762 if isinstance(result, ooberror):
756 return result
763 return result
757
764
758 # For now, all batchable commands must return bytesresponse or
765 # For now, all batchable commands must return bytesresponse or
759 # raw bytes (for backwards compatibility).
766 # raw bytes (for backwards compatibility).
760 assert isinstance(result, (bytesresponse, bytes))
767 assert isinstance(result, (bytesresponse, bytes))
761 if isinstance(result, bytesresponse):
768 if isinstance(result, bytesresponse):
762 result = result.data
769 result = result.data
763 res.append(escapearg(result))
770 res.append(escapearg(result))
764
771
765 return bytesresponse(';'.join(res))
772 return bytesresponse(';'.join(res))
766
773
767 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY,
774 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY,
768 permission='pull')
775 permission='pull')
769 def between(repo, proto, pairs):
776 def between(repo, proto, pairs):
770 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
777 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
771 r = []
778 r = []
772 for b in repo.between(pairs):
779 for b in repo.between(pairs):
773 r.append(encodelist(b) + "\n")
780 r.append(encodelist(b) + "\n")
774
781
775 return bytesresponse(''.join(r))
782 return bytesresponse(''.join(r))
776
783
777 @wireprotocommand('branchmap', permission='pull')
784 @wireprotocommand('branchmap', permission='pull')
778 def branchmap(repo, proto):
785 def branchmap(repo, proto):
779 branchmap = repo.branchmap()
786 branchmap = repo.branchmap()
780 heads = []
787 heads = []
781 for branch, nodes in branchmap.iteritems():
788 for branch, nodes in branchmap.iteritems():
782 branchname = urlreq.quote(encoding.fromlocal(branch))
789 branchname = urlreq.quote(encoding.fromlocal(branch))
783 branchnodes = encodelist(nodes)
790 branchnodes = encodelist(nodes)
784 heads.append('%s %s' % (branchname, branchnodes))
791 heads.append('%s %s' % (branchname, branchnodes))
785
792
786 return bytesresponse('\n'.join(heads))
793 return bytesresponse('\n'.join(heads))
787
794
788 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY,
795 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY,
789 permission='pull')
796 permission='pull')
790 def branches(repo, proto, nodes):
797 def branches(repo, proto, nodes):
791 nodes = decodelist(nodes)
798 nodes = decodelist(nodes)
792 r = []
799 r = []
793 for b in repo.branches(nodes):
800 for b in repo.branches(nodes):
794 r.append(encodelist(b) + "\n")
801 r.append(encodelist(b) + "\n")
795
802
796 return bytesresponse(''.join(r))
803 return bytesresponse(''.join(r))
797
804
798 @wireprotocommand('clonebundles', '', permission='pull')
805 @wireprotocommand('clonebundles', '', permission='pull')
799 def clonebundles(repo, proto):
806 def clonebundles(repo, proto):
800 """Server command for returning info for available bundles to seed clones.
807 """Server command for returning info for available bundles to seed clones.
801
808
802 Clients will parse this response and determine what bundle to fetch.
809 Clients will parse this response and determine what bundle to fetch.
803
810
804 Extensions may wrap this command to filter or dynamically emit data
811 Extensions may wrap this command to filter or dynamically emit data
805 depending on the request. e.g. you could advertise URLs for the closest
812 depending on the request. e.g. you could advertise URLs for the closest
806 data center given the client's IP address.
813 data center given the client's IP address.
807 """
814 """
808 return bytesresponse(repo.vfs.tryread('clonebundles.manifest'))
815 return bytesresponse(repo.vfs.tryread('clonebundles.manifest'))
809
816
810 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
817 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
811 'known', 'getbundle', 'unbundlehash', 'batch']
818 'known', 'getbundle', 'unbundlehash', 'batch']
812
819
813 def _capabilities(repo, proto):
820 def _capabilities(repo, proto):
814 """return a list of capabilities for a repo
821 """return a list of capabilities for a repo
815
822
816 This function exists to allow extensions to easily wrap capabilities
823 This function exists to allow extensions to easily wrap capabilities
817 computation
824 computation
818
825
819 - returns a lists: easy to alter
826 - returns a lists: easy to alter
820 - change done here will be propagated to both `capabilities` and `hello`
827 - change done here will be propagated to both `capabilities` and `hello`
821 command without any other action needed.
828 command without any other action needed.
822 """
829 """
823 # copy to prevent modification of the global list
830 # copy to prevent modification of the global list
824 caps = list(wireprotocaps)
831 caps = list(wireprotocaps)
825
832
826 # Command of same name as capability isn't exposed to version 1 of
833 # Command of same name as capability isn't exposed to version 1 of
827 # transports. So conditionally add it.
834 # transports. So conditionally add it.
828 if commands.commandavailable('changegroupsubset', proto):
835 if commands.commandavailable('changegroupsubset', proto):
829 caps.append('changegroupsubset')
836 caps.append('changegroupsubset')
830
837
831 if streamclone.allowservergeneration(repo):
838 if streamclone.allowservergeneration(repo):
832 if repo.ui.configbool('server', 'preferuncompressed'):
839 if repo.ui.configbool('server', 'preferuncompressed'):
833 caps.append('stream-preferred')
840 caps.append('stream-preferred')
834 requiredformats = repo.requirements & repo.supportedformats
841 requiredformats = repo.requirements & repo.supportedformats
835 # if our local revlogs are just revlogv1, add 'stream' cap
842 # if our local revlogs are just revlogv1, add 'stream' cap
836 if not requiredformats - {'revlogv1'}:
843 if not requiredformats - {'revlogv1'}:
837 caps.append('stream')
844 caps.append('stream')
838 # otherwise, add 'streamreqs' detailing our local revlog format
845 # otherwise, add 'streamreqs' detailing our local revlog format
839 else:
846 else:
840 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
847 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
841 if repo.ui.configbool('experimental', 'bundle2-advertise'):
848 if repo.ui.configbool('experimental', 'bundle2-advertise'):
842 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
849 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
843 caps.append('bundle2=' + urlreq.quote(capsblob))
850 caps.append('bundle2=' + urlreq.quote(capsblob))
844 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
851 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
845
852
846 return proto.addcapabilities(repo, caps)
853 return proto.addcapabilities(repo, caps)
847
854
848 # If you are writing an extension and consider wrapping this function. Wrap
855 # If you are writing an extension and consider wrapping this function. Wrap
849 # `_capabilities` instead.
856 # `_capabilities` instead.
850 @wireprotocommand('capabilities', permission='pull')
857 @wireprotocommand('capabilities', permission='pull')
851 def capabilities(repo, proto):
858 def capabilities(repo, proto):
852 return bytesresponse(' '.join(_capabilities(repo, proto)))
859 return bytesresponse(' '.join(_capabilities(repo, proto)))
853
860
854 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY,
861 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY,
855 permission='pull')
862 permission='pull')
856 def changegroup(repo, proto, roots):
863 def changegroup(repo, proto, roots):
857 nodes = decodelist(roots)
864 nodes = decodelist(roots)
858 outgoing = discovery.outgoing(repo, missingroots=nodes,
865 outgoing = discovery.outgoing(repo, missingroots=nodes,
859 missingheads=repo.heads())
866 missingheads=repo.heads())
860 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
867 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
861 gen = iter(lambda: cg.read(32768), '')
868 gen = iter(lambda: cg.read(32768), '')
862 return streamres(gen=gen)
869 return streamres(gen=gen)
863
870
864 @wireprotocommand('changegroupsubset', 'bases heads',
871 @wireprotocommand('changegroupsubset', 'bases heads',
865 transportpolicy=POLICY_V1_ONLY,
872 transportpolicy=POLICY_V1_ONLY,
866 permission='pull')
873 permission='pull')
867 def changegroupsubset(repo, proto, bases, heads):
874 def changegroupsubset(repo, proto, bases, heads):
868 bases = decodelist(bases)
875 bases = decodelist(bases)
869 heads = decodelist(heads)
876 heads = decodelist(heads)
870 outgoing = discovery.outgoing(repo, missingroots=bases,
877 outgoing = discovery.outgoing(repo, missingroots=bases,
871 missingheads=heads)
878 missingheads=heads)
872 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
879 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
873 gen = iter(lambda: cg.read(32768), '')
880 gen = iter(lambda: cg.read(32768), '')
874 return streamres(gen=gen)
881 return streamres(gen=gen)
875
882
876 @wireprotocommand('debugwireargs', 'one two *',
883 @wireprotocommand('debugwireargs', 'one two *',
877 permission='pull')
884 permission='pull')
878 def debugwireargs(repo, proto, one, two, others):
885 def debugwireargs(repo, proto, one, two, others):
879 # only accept optional args from the known set
886 # only accept optional args from the known set
880 opts = options('debugwireargs', ['three', 'four'], others)
887 opts = options('debugwireargs', ['three', 'four'], others)
881 return bytesresponse(repo.debugwireargs(one, two,
888 return bytesresponse(repo.debugwireargs(one, two,
882 **pycompat.strkwargs(opts)))
889 **pycompat.strkwargs(opts)))
883
890
884 @wireprotocommand('getbundle', '*', permission='pull')
891 @wireprotocommand('getbundle', '*', permission='pull')
885 def getbundle(repo, proto, others):
892 def getbundle(repo, proto, others):
886 opts = options('getbundle', gboptsmap.keys(), others)
893 opts = options('getbundle', gboptsmap.keys(), others)
887 for k, v in opts.iteritems():
894 for k, v in opts.iteritems():
888 keytype = gboptsmap[k]
895 keytype = gboptsmap[k]
889 if keytype == 'nodes':
896 if keytype == 'nodes':
890 opts[k] = decodelist(v)
897 opts[k] = decodelist(v)
891 elif keytype == 'csv':
898 elif keytype == 'csv':
892 opts[k] = list(v.split(','))
899 opts[k] = list(v.split(','))
893 elif keytype == 'scsv':
900 elif keytype == 'scsv':
894 opts[k] = set(v.split(','))
901 opts[k] = set(v.split(','))
895 elif keytype == 'boolean':
902 elif keytype == 'boolean':
896 # Client should serialize False as '0', which is a non-empty string
903 # Client should serialize False as '0', which is a non-empty string
897 # so it evaluates as a True bool.
904 # so it evaluates as a True bool.
898 if v == '0':
905 if v == '0':
899 opts[k] = False
906 opts[k] = False
900 else:
907 else:
901 opts[k] = bool(v)
908 opts[k] = bool(v)
902 elif keytype != 'plain':
909 elif keytype != 'plain':
903 raise KeyError('unknown getbundle option type %s'
910 raise KeyError('unknown getbundle option type %s'
904 % keytype)
911 % keytype)
905
912
906 if not bundle1allowed(repo, 'pull'):
913 if not bundle1allowed(repo, 'pull'):
907 if not exchange.bundle2requested(opts.get('bundlecaps')):
914 if not exchange.bundle2requested(opts.get('bundlecaps')):
908 if proto.name == 'http-v1':
915 if proto.name == 'http-v1':
909 return ooberror(bundle2required)
916 return ooberror(bundle2required)
910 raise error.Abort(bundle2requiredmain,
917 raise error.Abort(bundle2requiredmain,
911 hint=bundle2requiredhint)
918 hint=bundle2requiredhint)
912
919
913 prefercompressed = True
920 prefercompressed = True
914
921
915 try:
922 try:
916 if repo.ui.configbool('server', 'disablefullbundle'):
923 if repo.ui.configbool('server', 'disablefullbundle'):
917 # Check to see if this is a full clone.
924 # Check to see if this is a full clone.
918 clheads = set(repo.changelog.heads())
925 clheads = set(repo.changelog.heads())
919 changegroup = opts.get('cg', True)
926 changegroup = opts.get('cg', True)
920 heads = set(opts.get('heads', set()))
927 heads = set(opts.get('heads', set()))
921 common = set(opts.get('common', set()))
928 common = set(opts.get('common', set()))
922 common.discard(nullid)
929 common.discard(nullid)
923 if changegroup and not common and clheads == heads:
930 if changegroup and not common and clheads == heads:
924 raise error.Abort(
931 raise error.Abort(
925 _('server has pull-based clones disabled'),
932 _('server has pull-based clones disabled'),
926 hint=_('remove --pull if specified or upgrade Mercurial'))
933 hint=_('remove --pull if specified or upgrade Mercurial'))
927
934
928 info, chunks = exchange.getbundlechunks(repo, 'serve',
935 info, chunks = exchange.getbundlechunks(repo, 'serve',
929 **pycompat.strkwargs(opts))
936 **pycompat.strkwargs(opts))
930 prefercompressed = info.get('prefercompressed', True)
937 prefercompressed = info.get('prefercompressed', True)
931 except error.Abort as exc:
938 except error.Abort as exc:
932 # cleanly forward Abort error to the client
939 # cleanly forward Abort error to the client
933 if not exchange.bundle2requested(opts.get('bundlecaps')):
940 if not exchange.bundle2requested(opts.get('bundlecaps')):
934 if proto.name == 'http-v1':
941 if proto.name == 'http-v1':
935 return ooberror(pycompat.bytestr(exc) + '\n')
942 return ooberror(pycompat.bytestr(exc) + '\n')
936 raise # cannot do better for bundle1 + ssh
943 raise # cannot do better for bundle1 + ssh
937 # bundle2 request expect a bundle2 reply
944 # bundle2 request expect a bundle2 reply
938 bundler = bundle2.bundle20(repo.ui)
945 bundler = bundle2.bundle20(repo.ui)
939 manargs = [('message', pycompat.bytestr(exc))]
946 manargs = [('message', pycompat.bytestr(exc))]
940 advargs = []
947 advargs = []
941 if exc.hint is not None:
948 if exc.hint is not None:
942 advargs.append(('hint', exc.hint))
949 advargs.append(('hint', exc.hint))
943 bundler.addpart(bundle2.bundlepart('error:abort',
950 bundler.addpart(bundle2.bundlepart('error:abort',
944 manargs, advargs))
951 manargs, advargs))
945 chunks = bundler.getchunks()
952 chunks = bundler.getchunks()
946 prefercompressed = False
953 prefercompressed = False
947
954
948 return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
955 return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
949
956
950 @wireprotocommand('heads', permission='pull')
957 @wireprotocommand('heads', permission='pull')
951 def heads(repo, proto):
958 def heads(repo, proto):
952 h = repo.heads()
959 h = repo.heads()
953 return bytesresponse(encodelist(h) + '\n')
960 return bytesresponse(encodelist(h) + '\n')
954
961
955 @wireprotocommand('hello', permission='pull')
962 @wireprotocommand('hello', permission='pull')
956 def hello(repo, proto):
963 def hello(repo, proto):
957 """Called as part of SSH handshake to obtain server info.
964 """Called as part of SSH handshake to obtain server info.
958
965
959 Returns a list of lines describing interesting things about the
966 Returns a list of lines describing interesting things about the
960 server, in an RFC822-like format.
967 server, in an RFC822-like format.
961
968
962 Currently, the only one defined is ``capabilities``, which consists of a
969 Currently, the only one defined is ``capabilities``, which consists of a
963 line of space separated tokens describing server abilities:
970 line of space separated tokens describing server abilities:
964
971
965 capabilities: <token0> <token1> <token2>
972 capabilities: <token0> <token1> <token2>
966 """
973 """
967 caps = capabilities(repo, proto).data
974 caps = capabilities(repo, proto).data
968 return bytesresponse('capabilities: %s\n' % caps)
975 return bytesresponse('capabilities: %s\n' % caps)
969
976
970 @wireprotocommand('listkeys', 'namespace', permission='pull')
977 @wireprotocommand('listkeys', 'namespace', permission='pull')
971 def listkeys(repo, proto, namespace):
978 def listkeys(repo, proto, namespace):
972 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
979 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
973 return bytesresponse(pushkeymod.encodekeys(d))
980 return bytesresponse(pushkeymod.encodekeys(d))
974
981
975 @wireprotocommand('lookup', 'key', permission='pull')
982 @wireprotocommand('lookup', 'key', permission='pull')
976 def lookup(repo, proto, key):
983 def lookup(repo, proto, key):
977 try:
984 try:
978 k = encoding.tolocal(key)
985 k = encoding.tolocal(key)
979 c = repo[k]
986 c = repo[k]
980 r = c.hex()
987 r = c.hex()
981 success = 1
988 success = 1
982 except Exception as inst:
989 except Exception as inst:
983 r = util.forcebytestr(inst)
990 r = util.forcebytestr(inst)
984 success = 0
991 success = 0
985 return bytesresponse('%d %s\n' % (success, r))
992 return bytesresponse('%d %s\n' % (success, r))
986
993
987 @wireprotocommand('known', 'nodes *', permission='pull')
994 @wireprotocommand('known', 'nodes *', permission='pull')
988 def known(repo, proto, nodes, others):
995 def known(repo, proto, nodes, others):
989 v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
996 v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
990 return bytesresponse(v)
997 return bytesresponse(v)
991
998
992 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
999 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
993 def pushkey(repo, proto, namespace, key, old, new):
1000 def pushkey(repo, proto, namespace, key, old, new):
994 # compatibility with pre-1.8 clients which were accidentally
1001 # compatibility with pre-1.8 clients which were accidentally
995 # sending raw binary nodes rather than utf-8-encoded hex
1002 # sending raw binary nodes rather than utf-8-encoded hex
996 if len(new) == 20 and util.escapestr(new) != new:
1003 if len(new) == 20 and util.escapestr(new) != new:
997 # looks like it could be a binary node
1004 # looks like it could be a binary node
998 try:
1005 try:
999 new.decode('utf-8')
1006 new.decode('utf-8')
1000 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
1007 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
1001 except UnicodeDecodeError:
1008 except UnicodeDecodeError:
1002 pass # binary, leave unmodified
1009 pass # binary, leave unmodified
1003 else:
1010 else:
1004 new = encoding.tolocal(new) # normal path
1011 new = encoding.tolocal(new) # normal path
1005
1012
1006 with proto.mayberedirectstdio() as output:
1013 with proto.mayberedirectstdio() as output:
1007 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
1014 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
1008 encoding.tolocal(old), new) or False
1015 encoding.tolocal(old), new) or False
1009
1016
1010 output = output.getvalue() if output else ''
1017 output = output.getvalue() if output else ''
1011 return bytesresponse('%d\n%s' % (int(r), output))
1018 return bytesresponse('%d\n%s' % (int(r), output))
1012
1019
1013 @wireprotocommand('stream_out', permission='pull')
1020 @wireprotocommand('stream_out', permission='pull')
1014 def stream(repo, proto):
1021 def stream(repo, proto):
1015 '''If the server supports streaming clone, it advertises the "stream"
1022 '''If the server supports streaming clone, it advertises the "stream"
1016 capability with a value representing the version and flags of the repo
1023 capability with a value representing the version and flags of the repo
1017 it is serving. Client checks to see if it understands the format.
1024 it is serving. Client checks to see if it understands the format.
1018 '''
1025 '''
1019 return streamres_legacy(streamclone.generatev1wireproto(repo))
1026 return streamres_legacy(streamclone.generatev1wireproto(repo))
1020
1027
1021 @wireprotocommand('unbundle', 'heads', permission='push')
1028 @wireprotocommand('unbundle', 'heads', permission='push')
1022 def unbundle(repo, proto, heads):
1029 def unbundle(repo, proto, heads):
1023 their_heads = decodelist(heads)
1030 their_heads = decodelist(heads)
1024
1031
1025 with proto.mayberedirectstdio() as output:
1032 with proto.mayberedirectstdio() as output:
1026 try:
1033 try:
1027 exchange.check_heads(repo, their_heads, 'preparing changes')
1034 exchange.check_heads(repo, their_heads, 'preparing changes')
1028
1035
1029 # write bundle data to temporary file because it can be big
1036 # write bundle data to temporary file because it can be big
1030 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1037 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1031 fp = os.fdopen(fd, r'wb+')
1038 fp = os.fdopen(fd, r'wb+')
1032 r = 0
1039 r = 0
1033 try:
1040 try:
1034 proto.forwardpayload(fp)
1041 proto.forwardpayload(fp)
1035 fp.seek(0)
1042 fp.seek(0)
1036 gen = exchange.readbundle(repo.ui, fp, None)
1043 gen = exchange.readbundle(repo.ui, fp, None)
1037 if (isinstance(gen, changegroupmod.cg1unpacker)
1044 if (isinstance(gen, changegroupmod.cg1unpacker)
1038 and not bundle1allowed(repo, 'push')):
1045 and not bundle1allowed(repo, 'push')):
1039 if proto.name == 'http-v1':
1046 if proto.name == 'http-v1':
1040 # need to special case http because stderr do not get to
1047 # need to special case http because stderr do not get to
1041 # the http client on failed push so we need to abuse
1048 # the http client on failed push so we need to abuse
1042 # some other error type to make sure the message get to
1049 # some other error type to make sure the message get to
1043 # the user.
1050 # the user.
1044 return ooberror(bundle2required)
1051 return ooberror(bundle2required)
1045 raise error.Abort(bundle2requiredmain,
1052 raise error.Abort(bundle2requiredmain,
1046 hint=bundle2requiredhint)
1053 hint=bundle2requiredhint)
1047
1054
1048 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1055 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1049 proto.client())
1056 proto.client())
1050 if util.safehasattr(r, 'addpart'):
1057 if util.safehasattr(r, 'addpart'):
1051 # The return looks streamable, we are in the bundle2 case
1058 # The return looks streamable, we are in the bundle2 case
1052 # and should return a stream.
1059 # and should return a stream.
1053 return streamres_legacy(gen=r.getchunks())
1060 return streamres_legacy(gen=r.getchunks())
1054 return pushres(r, output.getvalue() if output else '')
1061 return pushres(r, output.getvalue() if output else '')
1055
1062
1056 finally:
1063 finally:
1057 fp.close()
1064 fp.close()
1058 os.unlink(tempname)
1065 os.unlink(tempname)
1059
1066
1060 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1067 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1061 # handle non-bundle2 case first
1068 # handle non-bundle2 case first
1062 if not getattr(exc, 'duringunbundle2', False):
1069 if not getattr(exc, 'duringunbundle2', False):
1063 try:
1070 try:
1064 raise
1071 raise
1065 except error.Abort:
1072 except error.Abort:
1066 # The old code we moved used util.stderr directly.
1073 # The old code we moved used util.stderr directly.
1067 # We did not change it to minimise code change.
1074 # We did not change it to minimise code change.
1068 # This need to be moved to something proper.
1075 # This need to be moved to something proper.
1069 # Feel free to do it.
1076 # Feel free to do it.
1070 util.stderr.write("abort: %s\n" % exc)
1077 util.stderr.write("abort: %s\n" % exc)
1071 if exc.hint is not None:
1078 if exc.hint is not None:
1072 util.stderr.write("(%s)\n" % exc.hint)
1079 util.stderr.write("(%s)\n" % exc.hint)
1073 return pushres(0, output.getvalue() if output else '')
1080 return pushres(0, output.getvalue() if output else '')
1074 except error.PushRaced:
1081 except error.PushRaced:
1075 return pusherr(pycompat.bytestr(exc),
1082 return pusherr(pycompat.bytestr(exc),
1076 output.getvalue() if output else '')
1083 output.getvalue() if output else '')
1077
1084
1078 bundler = bundle2.bundle20(repo.ui)
1085 bundler = bundle2.bundle20(repo.ui)
1079 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1086 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1080 bundler.addpart(out)
1087 bundler.addpart(out)
1081 try:
1088 try:
1082 try:
1089 try:
1083 raise
1090 raise
1084 except error.PushkeyFailed as exc:
1091 except error.PushkeyFailed as exc:
1085 # check client caps
1092 # check client caps
1086 remotecaps = getattr(exc, '_replycaps', None)
1093 remotecaps = getattr(exc, '_replycaps', None)
1087 if (remotecaps is not None
1094 if (remotecaps is not None
1088 and 'pushkey' not in remotecaps.get('error', ())):
1095 and 'pushkey' not in remotecaps.get('error', ())):
1089 # no support remote side, fallback to Abort handler.
1096 # no support remote side, fallback to Abort handler.
1090 raise
1097 raise
1091 part = bundler.newpart('error:pushkey')
1098 part = bundler.newpart('error:pushkey')
1092 part.addparam('in-reply-to', exc.partid)
1099 part.addparam('in-reply-to', exc.partid)
1093 if exc.namespace is not None:
1100 if exc.namespace is not None:
1094 part.addparam('namespace', exc.namespace,
1101 part.addparam('namespace', exc.namespace,
1095 mandatory=False)
1102 mandatory=False)
1096 if exc.key is not None:
1103 if exc.key is not None:
1097 part.addparam('key', exc.key, mandatory=False)
1104 part.addparam('key', exc.key, mandatory=False)
1098 if exc.new is not None:
1105 if exc.new is not None:
1099 part.addparam('new', exc.new, mandatory=False)
1106 part.addparam('new', exc.new, mandatory=False)
1100 if exc.old is not None:
1107 if exc.old is not None:
1101 part.addparam('old', exc.old, mandatory=False)
1108 part.addparam('old', exc.old, mandatory=False)
1102 if exc.ret is not None:
1109 if exc.ret is not None:
1103 part.addparam('ret', exc.ret, mandatory=False)
1110 part.addparam('ret', exc.ret, mandatory=False)
1104 except error.BundleValueError as exc:
1111 except error.BundleValueError as exc:
1105 errpart = bundler.newpart('error:unsupportedcontent')
1112 errpart = bundler.newpart('error:unsupportedcontent')
1106 if exc.parttype is not None:
1113 if exc.parttype is not None:
1107 errpart.addparam('parttype', exc.parttype)
1114 errpart.addparam('parttype', exc.parttype)
1108 if exc.params:
1115 if exc.params:
1109 errpart.addparam('params', '\0'.join(exc.params))
1116 errpart.addparam('params', '\0'.join(exc.params))
1110 except error.Abort as exc:
1117 except error.Abort as exc:
1111 manargs = [('message', util.forcebytestr(exc))]
1118 manargs = [('message', util.forcebytestr(exc))]
1112 advargs = []
1119 advargs = []
1113 if exc.hint is not None:
1120 if exc.hint is not None:
1114 advargs.append(('hint', exc.hint))
1121 advargs.append(('hint', exc.hint))
1115 bundler.addpart(bundle2.bundlepart('error:abort',
1122 bundler.addpart(bundle2.bundlepart('error:abort',
1116 manargs, advargs))
1123 manargs, advargs))
1117 except error.PushRaced as exc:
1124 except error.PushRaced as exc:
1118 bundler.newpart('error:pushraced',
1125 bundler.newpart('error:pushraced',
1119 [('message', util.forcebytestr(exc))])
1126 [('message', util.forcebytestr(exc))])
1120 return streamres_legacy(gen=bundler.getchunks())
1127 return streamres_legacy(gen=bundler.getchunks())
@@ -1,552 +1,555 b''
1 #require killdaemons serve
1 #require killdaemons serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 $ echo foo>foo.d/foo
7 $ echo foo>foo.d/foo
8 $ echo bar>foo.d/bAr.hg.d/BaR
8 $ echo bar>foo.d/bAr.hg.d/BaR
9 $ echo bar>foo.d/baR.d.hg/bAR
9 $ echo bar>foo.d/baR.d.hg/bAR
10 $ hg commit -A -m 1
10 $ hg commit -A -m 1
11 adding foo
11 adding foo
12 adding foo.d/bAr.hg.d/BaR
12 adding foo.d/bAr.hg.d/BaR
13 adding foo.d/baR.d.hg/bAR
13 adding foo.d/baR.d.hg/bAR
14 adding foo.d/foo
14 adding foo.d/foo
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17
17
18 Test server address cannot be reused
18 Test server address cannot be reused
19
19
20 $ hg serve -p $HGPORT1 2>&1
20 $ hg serve -p $HGPORT1 2>&1
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 [255]
22 [255]
23
23
24 $ cd ..
24 $ cd ..
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26
26
27 clone via stream
27 clone via stream
28
28
29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
30 streaming all changes
30 streaming all changes
31 6 files to transfer, 606 bytes of data
31 6 files to transfer, 606 bytes of data
32 transferred * bytes in * seconds (*/sec) (glob)
32 transferred * bytes in * seconds (*/sec) (glob)
33 searching for changes
33 searching for changes
34 no changes found
34 no changes found
35 updating to branch default
35 updating to branch default
36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 $ hg verify -R copy
37 $ hg verify -R copy
38 checking changesets
38 checking changesets
39 checking manifests
39 checking manifests
40 crosschecking files in changesets and manifests
40 crosschecking files in changesets and manifests
41 checking files
41 checking files
42 4 files, 1 changesets, 4 total revisions
42 4 files, 1 changesets, 4 total revisions
43
43
44 try to clone via stream, should use pull instead
44 try to clone via stream, should use pull instead
45
45
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 warning: stream clone requested but server has them disabled
47 warning: stream clone requested but server has them disabled
48 requesting all changes
48 requesting all changes
49 adding changesets
49 adding changesets
50 adding manifests
50 adding manifests
51 adding file changes
51 adding file changes
52 added 1 changesets with 4 changes to 4 files
52 added 1 changesets with 4 changes to 4 files
53 new changesets 8b6053c928fe
53 new changesets 8b6053c928fe
54 updating to branch default
54 updating to branch default
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56
56
57 try to clone via stream but missing requirements, so should use pull instead
57 try to clone via stream but missing requirements, so should use pull instead
58
58
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 > from mercurial import localrepo
60 > from mercurial import localrepo
61 > def extsetup(ui):
61 > def extsetup(ui):
62 > localrepo.localrepository.supportedformats.remove('generaldelta')
62 > localrepo.localrepository.supportedformats.remove('generaldelta')
63 > EOF
63 > EOF
64
64
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 warning: stream clone requested but client is missing requirements: generaldelta
66 warning: stream clone requested but client is missing requirements: generaldelta
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 requesting all changes
68 requesting all changes
69 adding changesets
69 adding changesets
70 adding manifests
70 adding manifests
71 adding file changes
71 adding file changes
72 added 1 changesets with 4 changes to 4 files
72 added 1 changesets with 4 changes to 4 files
73 new changesets 8b6053c928fe
73 new changesets 8b6053c928fe
74 updating to branch default
74 updating to branch default
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76
76
77 clone via pull
77 clone via pull
78
78
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 requesting all changes
80 requesting all changes
81 adding changesets
81 adding changesets
82 adding manifests
82 adding manifests
83 adding file changes
83 adding file changes
84 added 1 changesets with 4 changes to 4 files
84 added 1 changesets with 4 changes to 4 files
85 new changesets 8b6053c928fe
85 new changesets 8b6053c928fe
86 updating to branch default
86 updating to branch default
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 $ hg verify -R copy-pull
88 $ hg verify -R copy-pull
89 checking changesets
89 checking changesets
90 checking manifests
90 checking manifests
91 crosschecking files in changesets and manifests
91 crosschecking files in changesets and manifests
92 checking files
92 checking files
93 4 files, 1 changesets, 4 total revisions
93 4 files, 1 changesets, 4 total revisions
94 $ cd test
94 $ cd test
95 $ echo bar > bar
95 $ echo bar > bar
96 $ hg commit -A -d '1 0' -m 2
96 $ hg commit -A -d '1 0' -m 2
97 adding bar
97 adding bar
98 $ cd ..
98 $ cd ..
99
99
100 clone over http with --update
100 clone over http with --update
101
101
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 requesting all changes
103 requesting all changes
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 2 changesets with 5 changes to 5 files
107 added 2 changesets with 5 changes to 5 files
108 new changesets 8b6053c928fe:5fed3813f7f5
108 new changesets 8b6053c928fe:5fed3813f7f5
109 updating to branch default
109 updating to branch default
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 $ hg log -r . -R updated
111 $ hg log -r . -R updated
112 changeset: 0:8b6053c928fe
112 changeset: 0:8b6053c928fe
113 user: test
113 user: test
114 date: Thu Jan 01 00:00:00 1970 +0000
114 date: Thu Jan 01 00:00:00 1970 +0000
115 summary: 1
115 summary: 1
116
116
117 $ rm -rf updated
117 $ rm -rf updated
118
118
119 incoming via HTTP
119 incoming via HTTP
120
120
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 adding changesets
122 adding changesets
123 adding manifests
123 adding manifests
124 adding file changes
124 adding file changes
125 added 1 changesets with 4 changes to 4 files
125 added 1 changesets with 4 changes to 4 files
126 new changesets 8b6053c928fe
126 new changesets 8b6053c928fe
127 updating to branch default
127 updating to branch default
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 $ cd partial
129 $ cd partial
130 $ touch LOCAL
130 $ touch LOCAL
131 $ hg ci -qAm LOCAL
131 $ hg ci -qAm LOCAL
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 comparing with http://localhost:$HGPORT1/
133 comparing with http://localhost:$HGPORT1/
134 searching for changes
134 searching for changes
135 2
135 2
136 $ cd ..
136 $ cd ..
137
137
138 pull
138 pull
139
139
140 $ cd copy-pull
140 $ cd copy-pull
141 $ cat >> .hg/hgrc <<EOF
141 $ cat >> .hg/hgrc <<EOF
142 > [hooks]
142 > [hooks]
143 > changegroup = sh -c "printenv.py changegroup"
143 > changegroup = sh -c "printenv.py changegroup"
144 > EOF
144 > EOF
145 $ hg pull
145 $ hg pull
146 pulling from http://localhost:$HGPORT1/
146 pulling from http://localhost:$HGPORT1/
147 searching for changes
147 searching for changes
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 1 changes to 1 files
151 added 1 changesets with 1 changes to 1 files
152 new changesets 5fed3813f7f5
152 new changesets 5fed3813f7f5
153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
154 (run 'hg update' to get a working copy)
154 (run 'hg update' to get a working copy)
155 $ cd ..
155 $ cd ..
156
156
157 clone from invalid URL
157 clone from invalid URL
158
158
159 $ hg clone http://localhost:$HGPORT/bad
159 $ hg clone http://localhost:$HGPORT/bad
160 abort: HTTP Error 404: Not Found
160 abort: HTTP Error 404: Not Found
161 [255]
161 [255]
162
162
163 test http authentication
163 test http authentication
164 + use the same server to test server side streaming preference
164 + use the same server to test server side streaming preference
165
165
166 $ cd test
166 $ cd test
167 $ cat << EOT > userpass.py
167 $ cat << EOT > userpass.py
168 > import base64
168 > import base64
169 > from mercurial.hgweb import common
169 > from mercurial.hgweb import common
170 > def perform_authentication(hgweb, req, op):
170 > def perform_authentication(hgweb, req, op):
171 > auth = req.headers.get('Authorization')
171 > auth = req.headers.get('Authorization')
172 > if not auth:
172 > if not auth:
173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
177 > def extsetup():
177 > def extsetup():
178 > common.permhooks.insert(0, perform_authentication)
178 > common.permhooks.insert(0, perform_authentication)
179 > EOT
179 > EOT
180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
181 > --config server.preferuncompressed=True \
181 > --config server.preferuncompressed=True \
182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
183 $ cat pid >> $DAEMON_PIDS
183 $ cat pid >> $DAEMON_PIDS
184
184
185 $ cat << EOF > get_pass.py
185 $ cat << EOF > get_pass.py
186 > import getpass
186 > import getpass
187 > def newgetpass(arg):
187 > def newgetpass(arg):
188 > return "pass"
188 > return "pass"
189 > getpass.getpass = newgetpass
189 > getpass.getpass = newgetpass
190 > EOF
190 > EOF
191
191
192 $ hg id http://localhost:$HGPORT2/
192 $ hg id http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
194 [255]
194 [255]
195 $ hg id http://localhost:$HGPORT2/
195 $ hg id http://localhost:$HGPORT2/
196 abort: http authorization required for http://localhost:$HGPORT2/
196 abort: http authorization required for http://localhost:$HGPORT2/
197 [255]
197 [255]
198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
199 http authorization required for http://localhost:$HGPORT2/
199 http authorization required for http://localhost:$HGPORT2/
200 realm: mercurial
200 realm: mercurial
201 user: user
201 user: user
202 password: 5fed3813f7f5
202 password: 5fed3813f7f5
203 $ hg id http://user:pass@localhost:$HGPORT2/
203 $ hg id http://user:pass@localhost:$HGPORT2/
204 5fed3813f7f5
204 5fed3813f7f5
205 $ echo '[auth]' >> .hg/hgrc
205 $ echo '[auth]' >> .hg/hgrc
206 $ echo 'l.schemes=http' >> .hg/hgrc
206 $ echo 'l.schemes=http' >> .hg/hgrc
207 $ echo 'l.prefix=lo' >> .hg/hgrc
207 $ echo 'l.prefix=lo' >> .hg/hgrc
208 $ echo 'l.username=user' >> .hg/hgrc
208 $ echo 'l.username=user' >> .hg/hgrc
209 $ echo 'l.password=pass' >> .hg/hgrc
209 $ echo 'l.password=pass' >> .hg/hgrc
210 $ hg id http://localhost:$HGPORT2/
210 $ hg id http://localhost:$HGPORT2/
211 5fed3813f7f5
211 5fed3813f7f5
212 $ hg id http://localhost:$HGPORT2/
212 $ hg id http://localhost:$HGPORT2/
213 5fed3813f7f5
213 5fed3813f7f5
214 $ hg id http://user@localhost:$HGPORT2/
214 $ hg id http://user@localhost:$HGPORT2/
215 5fed3813f7f5
215 5fed3813f7f5
216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
217 streaming all changes
217 streaming all changes
218 7 files to transfer, 916 bytes of data
218 7 files to transfer, 916 bytes of data
219 transferred * bytes in * seconds (*/sec) (glob)
219 transferred * bytes in * seconds (*/sec) (glob)
220 searching for changes
220 searching for changes
221 no changes found
221 no changes found
222 updating to branch default
222 updating to branch default
223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 --pull should override server's preferuncompressed
224 --pull should override server's preferuncompressed
225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
226 requesting all changes
226 requesting all changes
227 adding changesets
227 adding changesets
228 adding manifests
228 adding manifests
229 adding file changes
229 adding file changes
230 added 2 changesets with 5 changes to 5 files
230 added 2 changesets with 5 changes to 5 files
231 new changesets 8b6053c928fe:5fed3813f7f5
231 new changesets 8b6053c928fe:5fed3813f7f5
232 updating to branch default
232 updating to branch default
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234
234
235 $ hg id http://user2@localhost:$HGPORT2/
235 $ hg id http://user2@localhost:$HGPORT2/
236 abort: http authorization required for http://localhost:$HGPORT2/
236 abort: http authorization required for http://localhost:$HGPORT2/
237 [255]
237 [255]
238 $ hg id http://user:pass2@localhost:$HGPORT2/
238 $ hg id http://user:pass2@localhost:$HGPORT2/
239 abort: HTTP Error 403: no
239 abort: HTTP Error 403: no
240 [255]
240 [255]
241
241
242 $ hg -R dest tag -r tip top
242 $ hg -R dest tag -r tip top
243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
244 pushing to http://user:***@localhost:$HGPORT2/
244 pushing to http://user:***@localhost:$HGPORT2/
245 searching for changes
245 searching for changes
246 remote: adding changesets
246 remote: adding changesets
247 remote: adding manifests
247 remote: adding manifests
248 remote: adding file changes
248 remote: adding file changes
249 remote: added 1 changesets with 1 changes to 1 files
249 remote: added 1 changesets with 1 changes to 1 files
250 $ hg rollback -q
250 $ hg rollback -q
251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
252 pushing to http://user:***@localhost:$HGPORT2/
252 pushing to http://user:***@localhost:$HGPORT2/
253 using http://localhost:$HGPORT2/
253 using http://localhost:$HGPORT2/
254 http auth: user user, password ****
254 http auth: user user, password ****
255 sending capabilities command
255 sending capabilities command
256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
257 http auth: user user, password ****
257 http auth: user user, password ****
258 devel-peer-request: finished in *.???? seconds (200) (glob)
258 devel-peer-request: finished in *.???? seconds (200) (glob)
259 query 1; heads
259 query 1; heads
260 devel-peer-request: batched-content
261 devel-peer-request: - heads (0 arguments)
262 devel-peer-request: - known (1 arguments)
260 sending batch command
263 sending batch command
261 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
264 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
262 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
265 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
263 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
266 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
264 devel-peer-request: 68 bytes of commands arguments in headers
267 devel-peer-request: 68 bytes of commands arguments in headers
265 devel-peer-request: finished in *.???? seconds (200) (glob)
268 devel-peer-request: finished in *.???? seconds (200) (glob)
266 searching for changes
269 searching for changes
267 all remote heads known locally
270 all remote heads known locally
268 preparing listkeys for "phases"
271 preparing listkeys for "phases"
269 sending listkeys command
272 sending listkeys command
270 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
273 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
271 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
274 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
272 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
275 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 devel-peer-request: 16 bytes of commands arguments in headers
276 devel-peer-request: 16 bytes of commands arguments in headers
274 devel-peer-request: finished in *.???? seconds (200) (glob)
277 devel-peer-request: finished in *.???? seconds (200) (glob)
275 received listkey for "phases": 58 bytes
278 received listkey for "phases": 58 bytes
276 checking for updated bookmarks
279 checking for updated bookmarks
277 preparing listkeys for "bookmarks"
280 preparing listkeys for "bookmarks"
278 sending listkeys command
281 sending listkeys command
279 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
282 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
280 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
283 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
281 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
284 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
282 devel-peer-request: 19 bytes of commands arguments in headers
285 devel-peer-request: 19 bytes of commands arguments in headers
283 devel-peer-request: finished in *.???? seconds (200) (glob)
286 devel-peer-request: finished in *.???? seconds (200) (glob)
284 received listkey for "bookmarks": 0 bytes
287 received listkey for "bookmarks": 0 bytes
285 sending branchmap command
288 sending branchmap command
286 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
289 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
287 devel-peer-request: Vary X-HgProto-1
290 devel-peer-request: Vary X-HgProto-1
288 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
291 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 devel-peer-request: finished in *.???? seconds (200) (glob)
292 devel-peer-request: finished in *.???? seconds (200) (glob)
290 sending branchmap command
293 sending branchmap command
291 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
294 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
292 devel-peer-request: Vary X-HgProto-1
295 devel-peer-request: Vary X-HgProto-1
293 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
296 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 devel-peer-request: finished in *.???? seconds (200) (glob)
297 devel-peer-request: finished in *.???? seconds (200) (glob)
295 preparing listkeys for "bookmarks"
298 preparing listkeys for "bookmarks"
296 sending listkeys command
299 sending listkeys command
297 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
300 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
298 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
301 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
299 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
302 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 devel-peer-request: 19 bytes of commands arguments in headers
303 devel-peer-request: 19 bytes of commands arguments in headers
301 devel-peer-request: finished in *.???? seconds (200) (glob)
304 devel-peer-request: finished in *.???? seconds (200) (glob)
302 received listkey for "bookmarks": 0 bytes
305 received listkey for "bookmarks": 0 bytes
303 1 changesets found
306 1 changesets found
304 list of changesets:
307 list of changesets:
305 7f4e523d01f2cc3765ac8934da3d14db775ff872
308 7f4e523d01f2cc3765ac8934da3d14db775ff872
306 bundle2-output-bundle: "HG20", 5 parts total
309 bundle2-output-bundle: "HG20", 5 parts total
307 bundle2-output-part: "replycaps" 188 bytes payload
310 bundle2-output-part: "replycaps" 188 bytes payload
308 bundle2-output-part: "check:phases" 24 bytes payload
311 bundle2-output-part: "check:phases" 24 bytes payload
309 bundle2-output-part: "check:heads" streamed payload
312 bundle2-output-part: "check:heads" streamed payload
310 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
313 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
311 bundle2-output-part: "phase-heads" 24 bytes payload
314 bundle2-output-part: "phase-heads" 24 bytes payload
312 sending unbundle command
315 sending unbundle command
313 sending 996 bytes
316 sending 996 bytes
314 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
317 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
315 devel-peer-request: Content-length 996
318 devel-peer-request: Content-length 996
316 devel-peer-request: Content-type application/mercurial-0.1
319 devel-peer-request: Content-type application/mercurial-0.1
317 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
318 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
319 devel-peer-request: 16 bytes of commands arguments in headers
322 devel-peer-request: 16 bytes of commands arguments in headers
320 devel-peer-request: 996 bytes of data
323 devel-peer-request: 996 bytes of data
321 devel-peer-request: finished in *.???? seconds (200) (glob)
324 devel-peer-request: finished in *.???? seconds (200) (glob)
322 bundle2-input-bundle: no-transaction
325 bundle2-input-bundle: no-transaction
323 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
326 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
324 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
327 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
325 bundle2-input-part: total payload size 100
328 bundle2-input-part: total payload size 100
326 remote: adding changesets
329 remote: adding changesets
327 remote: adding manifests
330 remote: adding manifests
328 remote: adding file changes
331 remote: adding file changes
329 remote: added 1 changesets with 1 changes to 1 files
332 remote: added 1 changesets with 1 changes to 1 files
330 bundle2-input-part: "output" (advisory) supported
333 bundle2-input-part: "output" (advisory) supported
331 bundle2-input-bundle: 2 parts total
334 bundle2-input-bundle: 2 parts total
332 preparing listkeys for "phases"
335 preparing listkeys for "phases"
333 sending listkeys command
336 sending listkeys command
334 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
337 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
335 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
338 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
336 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
339 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
337 devel-peer-request: 16 bytes of commands arguments in headers
340 devel-peer-request: 16 bytes of commands arguments in headers
338 devel-peer-request: finished in *.???? seconds (200) (glob)
341 devel-peer-request: finished in *.???? seconds (200) (glob)
339 received listkey for "phases": 15 bytes
342 received listkey for "phases": 15 bytes
340 $ hg rollback -q
343 $ hg rollback -q
341
344
342 $ sed 's/.*] "/"/' < ../access.log
345 $ sed 's/.*] "/"/' < ../access.log
343 "GET /?cmd=capabilities HTTP/1.1" 401 -
346 "GET /?cmd=capabilities HTTP/1.1" 401 -
344 "GET /?cmd=capabilities HTTP/1.1" 401 -
347 "GET /?cmd=capabilities HTTP/1.1" 401 -
345 "GET /?cmd=capabilities HTTP/1.1" 401 -
348 "GET /?cmd=capabilities HTTP/1.1" 401 -
346 "GET /?cmd=capabilities HTTP/1.1" 200 -
349 "GET /?cmd=capabilities HTTP/1.1" 200 -
347 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
350 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
348 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
351 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
349 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
352 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
350 "GET /?cmd=capabilities HTTP/1.1" 401 -
353 "GET /?cmd=capabilities HTTP/1.1" 401 -
351 "GET /?cmd=capabilities HTTP/1.1" 200 -
354 "GET /?cmd=capabilities HTTP/1.1" 200 -
352 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
355 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
353 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
356 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
354 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
357 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
355 "GET /?cmd=capabilities HTTP/1.1" 401 -
358 "GET /?cmd=capabilities HTTP/1.1" 401 -
356 "GET /?cmd=capabilities HTTP/1.1" 200 -
359 "GET /?cmd=capabilities HTTP/1.1" 200 -
357 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
360 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
358 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
361 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
359 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
362 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
360 "GET /?cmd=capabilities HTTP/1.1" 401 -
363 "GET /?cmd=capabilities HTTP/1.1" 401 -
361 "GET /?cmd=capabilities HTTP/1.1" 200 -
364 "GET /?cmd=capabilities HTTP/1.1" 200 -
362 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
365 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
363 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
366 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
364 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
367 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
365 "GET /?cmd=capabilities HTTP/1.1" 401 -
368 "GET /?cmd=capabilities HTTP/1.1" 401 -
366 "GET /?cmd=capabilities HTTP/1.1" 200 -
369 "GET /?cmd=capabilities HTTP/1.1" 200 -
367 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
370 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
368 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
371 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
369 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
372 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
370 "GET /?cmd=capabilities HTTP/1.1" 401 -
373 "GET /?cmd=capabilities HTTP/1.1" 401 -
371 "GET /?cmd=capabilities HTTP/1.1" 200 -
374 "GET /?cmd=capabilities HTTP/1.1" 200 -
372 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
373 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
376 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
374 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
377 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
378 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
376 "GET /?cmd=capabilities HTTP/1.1" 401 -
379 "GET /?cmd=capabilities HTTP/1.1" 401 -
377 "GET /?cmd=capabilities HTTP/1.1" 200 -
380 "GET /?cmd=capabilities HTTP/1.1" 200 -
378 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
381 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
379 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
382 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
380 "GET /?cmd=capabilities HTTP/1.1" 401 -
383 "GET /?cmd=capabilities HTTP/1.1" 401 -
381 "GET /?cmd=capabilities HTTP/1.1" 401 -
384 "GET /?cmd=capabilities HTTP/1.1" 401 -
382 "GET /?cmd=capabilities HTTP/1.1" 403 -
385 "GET /?cmd=capabilities HTTP/1.1" 403 -
383 "GET /?cmd=capabilities HTTP/1.1" 401 -
386 "GET /?cmd=capabilities HTTP/1.1" 401 -
384 "GET /?cmd=capabilities HTTP/1.1" 200 -
387 "GET /?cmd=capabilities HTTP/1.1" 200 -
385 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
386 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
387 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
391 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
392 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
393 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
391 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
394 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
392 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
395 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
396 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 "GET /?cmd=capabilities HTTP/1.1" 200 -
397 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
403 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
404 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
405 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
403
406
404 $ cd ..
407 $ cd ..
405
408
406 clone of serve with repo in root and unserved subrepo (issue2970)
409 clone of serve with repo in root and unserved subrepo (issue2970)
407
410
408 $ hg --cwd test init sub
411 $ hg --cwd test init sub
409 $ echo empty > test/sub/empty
412 $ echo empty > test/sub/empty
410 $ hg --cwd test/sub add empty
413 $ hg --cwd test/sub add empty
411 $ hg --cwd test/sub commit -qm 'add empty'
414 $ hg --cwd test/sub commit -qm 'add empty'
412 $ hg --cwd test/sub tag -r 0 something
415 $ hg --cwd test/sub tag -r 0 something
413 $ echo sub = sub > test/.hgsub
416 $ echo sub = sub > test/.hgsub
414 $ hg --cwd test add .hgsub
417 $ hg --cwd test add .hgsub
415 $ hg --cwd test commit -qm 'add subrepo'
418 $ hg --cwd test commit -qm 'add subrepo'
416 $ hg clone http://localhost:$HGPORT noslash-clone
419 $ hg clone http://localhost:$HGPORT noslash-clone
417 requesting all changes
420 requesting all changes
418 adding changesets
421 adding changesets
419 adding manifests
422 adding manifests
420 adding file changes
423 adding file changes
421 added 3 changesets with 7 changes to 7 files
424 added 3 changesets with 7 changes to 7 files
422 new changesets 8b6053c928fe:56f9bc90cce6
425 new changesets 8b6053c928fe:56f9bc90cce6
423 updating to branch default
426 updating to branch default
424 abort: HTTP Error 404: Not Found
427 abort: HTTP Error 404: Not Found
425 [255]
428 [255]
426 $ hg clone http://localhost:$HGPORT/ slash-clone
429 $ hg clone http://localhost:$HGPORT/ slash-clone
427 requesting all changes
430 requesting all changes
428 adding changesets
431 adding changesets
429 adding manifests
432 adding manifests
430 adding file changes
433 adding file changes
431 added 3 changesets with 7 changes to 7 files
434 added 3 changesets with 7 changes to 7 files
432 new changesets 8b6053c928fe:56f9bc90cce6
435 new changesets 8b6053c928fe:56f9bc90cce6
433 updating to branch default
436 updating to branch default
434 abort: HTTP Error 404: Not Found
437 abort: HTTP Error 404: Not Found
435 [255]
438 [255]
436
439
437 check error log
440 check error log
438
441
439 $ cat error.log
442 $ cat error.log
440
443
441 check abort error reporting while pulling/cloning
444 check abort error reporting while pulling/cloning
442
445
443 $ $RUNTESTDIR/killdaemons.py
446 $ $RUNTESTDIR/killdaemons.py
444 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
447 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
445 $ cat hg3.pid >> $DAEMON_PIDS
448 $ cat hg3.pid >> $DAEMON_PIDS
446 $ hg clone http://localhost:$HGPORT/ abort-clone
449 $ hg clone http://localhost:$HGPORT/ abort-clone
447 requesting all changes
450 requesting all changes
448 remote: abort: this is an exercise
451 remote: abort: this is an exercise
449 abort: pull failed on remote
452 abort: pull failed on remote
450 [255]
453 [255]
451 $ cat error.log
454 $ cat error.log
452
455
453 disable pull-based clones
456 disable pull-based clones
454
457
455 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
458 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
456 $ cat hg4.pid >> $DAEMON_PIDS
459 $ cat hg4.pid >> $DAEMON_PIDS
457 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
460 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
458 requesting all changes
461 requesting all changes
459 remote: abort: server has pull-based clones disabled
462 remote: abort: server has pull-based clones disabled
460 abort: pull failed on remote
463 abort: pull failed on remote
461 (remove --pull if specified or upgrade Mercurial)
464 (remove --pull if specified or upgrade Mercurial)
462 [255]
465 [255]
463
466
464 ... but keep stream clones working
467 ... but keep stream clones working
465
468
466 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
469 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
467 streaming all changes
470 streaming all changes
468 * files to transfer, * of data (glob)
471 * files to transfer, * of data (glob)
469 transferred * in * seconds (*/sec) (glob)
472 transferred * in * seconds (*/sec) (glob)
470 searching for changes
473 searching for changes
471 no changes found
474 no changes found
472 $ cat error.log
475 $ cat error.log
473
476
474 ... and also keep partial clones and pulls working
477 ... and also keep partial clones and pulls working
475 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
478 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
476 adding changesets
479 adding changesets
477 adding manifests
480 adding manifests
478 adding file changes
481 adding file changes
479 added 1 changesets with 4 changes to 4 files
482 added 1 changesets with 4 changes to 4 files
480 new changesets 8b6053c928fe
483 new changesets 8b6053c928fe
481 updating to branch default
484 updating to branch default
482 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 $ hg pull -R test-partial-clone
486 $ hg pull -R test-partial-clone
484 pulling from http://localhost:$HGPORT1/
487 pulling from http://localhost:$HGPORT1/
485 searching for changes
488 searching for changes
486 adding changesets
489 adding changesets
487 adding manifests
490 adding manifests
488 adding file changes
491 adding file changes
489 added 2 changesets with 3 changes to 3 files
492 added 2 changesets with 3 changes to 3 files
490 new changesets 5fed3813f7f5:56f9bc90cce6
493 new changesets 5fed3813f7f5:56f9bc90cce6
491 (run 'hg update' to get a working copy)
494 (run 'hg update' to get a working copy)
492
495
493 corrupt cookies file should yield a warning
496 corrupt cookies file should yield a warning
494
497
495 $ cat > $TESTTMP/cookies.txt << EOF
498 $ cat > $TESTTMP/cookies.txt << EOF
496 > bad format
499 > bad format
497 > EOF
500 > EOF
498
501
499 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
502 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
500 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
503 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
501 56f9bc90cce6
504 56f9bc90cce6
502
505
503 $ killdaemons.py
506 $ killdaemons.py
504
507
505 Create dummy authentication handler that looks for cookies. It doesn't do anything
508 Create dummy authentication handler that looks for cookies. It doesn't do anything
506 useful. It just raises an HTTP 500 with details about the Cookie request header.
509 useful. It just raises an HTTP 500 with details about the Cookie request header.
507 We raise HTTP 500 because its message is printed in the abort message.
510 We raise HTTP 500 because its message is printed in the abort message.
508
511
509 $ cat > cookieauth.py << EOF
512 $ cat > cookieauth.py << EOF
510 > from mercurial import util
513 > from mercurial import util
511 > from mercurial.hgweb import common
514 > from mercurial.hgweb import common
512 > def perform_authentication(hgweb, req, op):
515 > def perform_authentication(hgweb, req, op):
513 > cookie = req.headers.get('Cookie')
516 > cookie = req.headers.get('Cookie')
514 > if not cookie:
517 > if not cookie:
515 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
518 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
516 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
519 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
517 > def extsetup():
520 > def extsetup():
518 > common.permhooks.insert(0, perform_authentication)
521 > common.permhooks.insert(0, perform_authentication)
519 > EOF
522 > EOF
520
523
521 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
524 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
522 $ cat pid > $DAEMON_PIDS
525 $ cat pid > $DAEMON_PIDS
523
526
524 Request without cookie sent should fail due to lack of cookie
527 Request without cookie sent should fail due to lack of cookie
525
528
526 $ hg id http://localhost:$HGPORT
529 $ hg id http://localhost:$HGPORT
527 abort: HTTP Error 500: no-cookie
530 abort: HTTP Error 500: no-cookie
528 [255]
531 [255]
529
532
530 Populate a cookies file
533 Populate a cookies file
531
534
532 $ cat > cookies.txt << EOF
535 $ cat > cookies.txt << EOF
533 > # HTTP Cookie File
536 > # HTTP Cookie File
534 > # Expiration is 2030-01-01 at midnight
537 > # Expiration is 2030-01-01 at midnight
535 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
538 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
536 > EOF
539 > EOF
537
540
538 Should not send a cookie for another domain
541 Should not send a cookie for another domain
539
542
540 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
543 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
541 abort: HTTP Error 500: no-cookie
544 abort: HTTP Error 500: no-cookie
542 [255]
545 [255]
543
546
544 Add a cookie entry for our test server and verify it is sent
547 Add a cookie entry for our test server and verify it is sent
545
548
546 $ cat >> cookies.txt << EOF
549 $ cat >> cookies.txt << EOF
547 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
550 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
548 > EOF
551 > EOF
549
552
550 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
553 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
551 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
554 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
552 [255]
555 [255]
@@ -1,640 +1,643 b''
1 #testcases sshv1 sshv2
1 #testcases sshv1 sshv2
2
2
3 #if sshv2
3 #if sshv2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [experimental]
5 > [experimental]
6 > sshpeer.advertise-v2 = true
6 > sshpeer.advertise-v2 = true
7 > sshserver.support-v2 = true
7 > sshserver.support-v2 = true
8 > EOF
8 > EOF
9 #endif
9 #endif
10
10
11 This test tries to exercise the ssh functionality with a dummy script
11 This test tries to exercise the ssh functionality with a dummy script
12
12
13 $ cat <<EOF >> $HGRCPATH
13 $ cat <<EOF >> $HGRCPATH
14 > [format]
14 > [format]
15 > usegeneraldelta=yes
15 > usegeneraldelta=yes
16 > EOF
16 > EOF
17
17
18 creating 'remote' repo
18 creating 'remote' repo
19
19
20 $ hg init remote
20 $ hg init remote
21 $ cd remote
21 $ cd remote
22 $ echo this > foo
22 $ echo this > foo
23 $ echo this > fooO
23 $ echo this > fooO
24 $ hg ci -A -m "init" foo fooO
24 $ hg ci -A -m "init" foo fooO
25
25
26 insert a closed branch (issue4428)
26 insert a closed branch (issue4428)
27
27
28 $ hg up null
28 $ hg up null
29 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
29 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
30 $ hg branch closed
30 $ hg branch closed
31 marked working directory as branch closed
31 marked working directory as branch closed
32 (branches are permanent and global, did you want a bookmark?)
32 (branches are permanent and global, did you want a bookmark?)
33 $ hg ci -mc0
33 $ hg ci -mc0
34 $ hg ci --close-branch -mc1
34 $ hg ci --close-branch -mc1
35 $ hg up -q default
35 $ hg up -q default
36
36
37 configure for serving
37 configure for serving
38
38
39 $ cat <<EOF > .hg/hgrc
39 $ cat <<EOF > .hg/hgrc
40 > [server]
40 > [server]
41 > uncompressed = True
41 > uncompressed = True
42 >
42 >
43 > [hooks]
43 > [hooks]
44 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
44 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
45 > EOF
45 > EOF
46 $ cd ..
46 $ cd ..
47
47
48 repo not found error
48 repo not found error
49
49
50 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
50 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
51 remote: abort: repository nonexistent not found!
51 remote: abort: repository nonexistent not found!
52 abort: no suitable response from remote hg!
52 abort: no suitable response from remote hg!
53 [255]
53 [255]
54
54
55 non-existent absolute path
55 non-existent absolute path
56
56
57 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
57 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
58 remote: abort: repository $TESTTMP/nonexistent not found!
58 remote: abort: repository $TESTTMP/nonexistent not found!
59 abort: no suitable response from remote hg!
59 abort: no suitable response from remote hg!
60 [255]
60 [255]
61
61
62 clone remote via stream
62 clone remote via stream
63
63
64 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
64 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
65 streaming all changes
65 streaming all changes
66 4 files to transfer, 602 bytes of data
66 4 files to transfer, 602 bytes of data
67 transferred 602 bytes in * seconds (*) (glob)
67 transferred 602 bytes in * seconds (*) (glob)
68 searching for changes
68 searching for changes
69 no changes found
69 no changes found
70 updating to branch default
70 updating to branch default
71 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 $ cd local-stream
72 $ cd local-stream
73 $ hg verify
73 $ hg verify
74 checking changesets
74 checking changesets
75 checking manifests
75 checking manifests
76 crosschecking files in changesets and manifests
76 crosschecking files in changesets and manifests
77 checking files
77 checking files
78 2 files, 3 changesets, 2 total revisions
78 2 files, 3 changesets, 2 total revisions
79 $ hg branches
79 $ hg branches
80 default 0:1160648e36ce
80 default 0:1160648e36ce
81 $ cd ..
81 $ cd ..
82
82
83 clone bookmarks via stream
83 clone bookmarks via stream
84
84
85 $ hg -R local-stream book mybook
85 $ hg -R local-stream book mybook
86 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
86 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
87 streaming all changes
87 streaming all changes
88 4 files to transfer, 602 bytes of data
88 4 files to transfer, 602 bytes of data
89 transferred 602 bytes in * seconds (*) (glob)
89 transferred 602 bytes in * seconds (*) (glob)
90 searching for changes
90 searching for changes
91 no changes found
91 no changes found
92 updating to branch default
92 updating to branch default
93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 $ cd stream2
94 $ cd stream2
95 $ hg book
95 $ hg book
96 mybook 0:1160648e36ce
96 mybook 0:1160648e36ce
97 $ cd ..
97 $ cd ..
98 $ rm -rf local-stream stream2
98 $ rm -rf local-stream stream2
99
99
100 clone remote via pull
100 clone remote via pull
101
101
102 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
102 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
103 requesting all changes
103 requesting all changes
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 3 changesets with 2 changes to 2 files
107 added 3 changesets with 2 changes to 2 files
108 new changesets 1160648e36ce:ad076bfb429d
108 new changesets 1160648e36ce:ad076bfb429d
109 updating to branch default
109 updating to branch default
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111
111
112 verify
112 verify
113
113
114 $ cd local
114 $ cd local
115 $ hg verify
115 $ hg verify
116 checking changesets
116 checking changesets
117 checking manifests
117 checking manifests
118 crosschecking files in changesets and manifests
118 crosschecking files in changesets and manifests
119 checking files
119 checking files
120 2 files, 3 changesets, 2 total revisions
120 2 files, 3 changesets, 2 total revisions
121 $ cat >> .hg/hgrc <<EOF
121 $ cat >> .hg/hgrc <<EOF
122 > [hooks]
122 > [hooks]
123 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
123 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
124 > EOF
124 > EOF
125
125
126 empty default pull
126 empty default pull
127
127
128 $ hg paths
128 $ hg paths
129 default = ssh://user@dummy/remote
129 default = ssh://user@dummy/remote
130 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
130 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
131 pulling from ssh://user@dummy/remote
131 pulling from ssh://user@dummy/remote
132 searching for changes
132 searching for changes
133 no changes found
133 no changes found
134
134
135 pull from wrong ssh URL
135 pull from wrong ssh URL
136
136
137 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
137 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
138 pulling from ssh://user@dummy/doesnotexist
138 pulling from ssh://user@dummy/doesnotexist
139 remote: abort: repository doesnotexist not found!
139 remote: abort: repository doesnotexist not found!
140 abort: no suitable response from remote hg!
140 abort: no suitable response from remote hg!
141 [255]
141 [255]
142
142
143 local change
143 local change
144
144
145 $ echo bleah > foo
145 $ echo bleah > foo
146 $ hg ci -m "add"
146 $ hg ci -m "add"
147
147
148 updating rc
148 updating rc
149
149
150 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
150 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
151 $ echo "[ui]" >> .hg/hgrc
151 $ echo "[ui]" >> .hg/hgrc
152 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
152 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
153
153
154 find outgoing
154 find outgoing
155
155
156 $ hg out ssh://user@dummy/remote
156 $ hg out ssh://user@dummy/remote
157 comparing with ssh://user@dummy/remote
157 comparing with ssh://user@dummy/remote
158 searching for changes
158 searching for changes
159 changeset: 3:a28a9d1a809c
159 changeset: 3:a28a9d1a809c
160 tag: tip
160 tag: tip
161 parent: 0:1160648e36ce
161 parent: 0:1160648e36ce
162 user: test
162 user: test
163 date: Thu Jan 01 00:00:00 1970 +0000
163 date: Thu Jan 01 00:00:00 1970 +0000
164 summary: add
164 summary: add
165
165
166
166
167 find incoming on the remote side
167 find incoming on the remote side
168
168
169 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
169 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
170 comparing with ssh://user@dummy/local
170 comparing with ssh://user@dummy/local
171 searching for changes
171 searching for changes
172 changeset: 3:a28a9d1a809c
172 changeset: 3:a28a9d1a809c
173 tag: tip
173 tag: tip
174 parent: 0:1160648e36ce
174 parent: 0:1160648e36ce
175 user: test
175 user: test
176 date: Thu Jan 01 00:00:00 1970 +0000
176 date: Thu Jan 01 00:00:00 1970 +0000
177 summary: add
177 summary: add
178
178
179
179
180 find incoming on the remote side (using absolute path)
180 find incoming on the remote side (using absolute path)
181
181
182 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
182 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
183 comparing with ssh://user@dummy/$TESTTMP/local
183 comparing with ssh://user@dummy/$TESTTMP/local
184 searching for changes
184 searching for changes
185 changeset: 3:a28a9d1a809c
185 changeset: 3:a28a9d1a809c
186 tag: tip
186 tag: tip
187 parent: 0:1160648e36ce
187 parent: 0:1160648e36ce
188 user: test
188 user: test
189 date: Thu Jan 01 00:00:00 1970 +0000
189 date: Thu Jan 01 00:00:00 1970 +0000
190 summary: add
190 summary: add
191
191
192
192
193 push
193 push
194
194
195 $ hg push
195 $ hg push
196 pushing to ssh://user@dummy/remote
196 pushing to ssh://user@dummy/remote
197 searching for changes
197 searching for changes
198 remote: adding changesets
198 remote: adding changesets
199 remote: adding manifests
199 remote: adding manifests
200 remote: adding file changes
200 remote: adding file changes
201 remote: added 1 changesets with 1 changes to 1 files
201 remote: added 1 changesets with 1 changes to 1 files
202 $ cd ../remote
202 $ cd ../remote
203
203
204 check remote tip
204 check remote tip
205
205
206 $ hg tip
206 $ hg tip
207 changeset: 3:a28a9d1a809c
207 changeset: 3:a28a9d1a809c
208 tag: tip
208 tag: tip
209 parent: 0:1160648e36ce
209 parent: 0:1160648e36ce
210 user: test
210 user: test
211 date: Thu Jan 01 00:00:00 1970 +0000
211 date: Thu Jan 01 00:00:00 1970 +0000
212 summary: add
212 summary: add
213
213
214 $ hg verify
214 $ hg verify
215 checking changesets
215 checking changesets
216 checking manifests
216 checking manifests
217 crosschecking files in changesets and manifests
217 crosschecking files in changesets and manifests
218 checking files
218 checking files
219 2 files, 4 changesets, 3 total revisions
219 2 files, 4 changesets, 3 total revisions
220 $ hg cat -r tip foo
220 $ hg cat -r tip foo
221 bleah
221 bleah
222 $ echo z > z
222 $ echo z > z
223 $ hg ci -A -m z z
223 $ hg ci -A -m z z
224 created new head
224 created new head
225
225
226 test pushkeys and bookmarks
226 test pushkeys and bookmarks
227
227
228 $ cd ../local
228 $ cd ../local
229 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
229 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
230 bookmarks
230 bookmarks
231 namespaces
231 namespaces
232 phases
232 phases
233 $ hg book foo -r 0
233 $ hg book foo -r 0
234 $ hg out -B
234 $ hg out -B
235 comparing with ssh://user@dummy/remote
235 comparing with ssh://user@dummy/remote
236 searching for changed bookmarks
236 searching for changed bookmarks
237 foo 1160648e36ce
237 foo 1160648e36ce
238 $ hg push -B foo
238 $ hg push -B foo
239 pushing to ssh://user@dummy/remote
239 pushing to ssh://user@dummy/remote
240 searching for changes
240 searching for changes
241 no changes found
241 no changes found
242 exporting bookmark foo
242 exporting bookmark foo
243 [1]
243 [1]
244 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
244 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
245 foo 1160648e36cec0054048a7edc4110c6f84fde594
245 foo 1160648e36cec0054048a7edc4110c6f84fde594
246 $ hg book -f foo
246 $ hg book -f foo
247 $ hg push --traceback
247 $ hg push --traceback
248 pushing to ssh://user@dummy/remote
248 pushing to ssh://user@dummy/remote
249 searching for changes
249 searching for changes
250 no changes found
250 no changes found
251 updating bookmark foo
251 updating bookmark foo
252 [1]
252 [1]
253 $ hg book -d foo
253 $ hg book -d foo
254 $ hg in -B
254 $ hg in -B
255 comparing with ssh://user@dummy/remote
255 comparing with ssh://user@dummy/remote
256 searching for changed bookmarks
256 searching for changed bookmarks
257 foo a28a9d1a809c
257 foo a28a9d1a809c
258 $ hg book -f -r 0 foo
258 $ hg book -f -r 0 foo
259 $ hg pull -B foo
259 $ hg pull -B foo
260 pulling from ssh://user@dummy/remote
260 pulling from ssh://user@dummy/remote
261 no changes found
261 no changes found
262 updating bookmark foo
262 updating bookmark foo
263 $ hg book -d foo
263 $ hg book -d foo
264 $ hg push -B foo
264 $ hg push -B foo
265 pushing to ssh://user@dummy/remote
265 pushing to ssh://user@dummy/remote
266 searching for changes
266 searching for changes
267 no changes found
267 no changes found
268 deleting remote bookmark foo
268 deleting remote bookmark foo
269 [1]
269 [1]
270
270
271 a bad, evil hook that prints to stdout
271 a bad, evil hook that prints to stdout
272
272
273 $ cat <<EOF > $TESTTMP/badhook
273 $ cat <<EOF > $TESTTMP/badhook
274 > import sys
274 > import sys
275 > sys.stdout.write("KABOOM\n")
275 > sys.stdout.write("KABOOM\n")
276 > EOF
276 > EOF
277
277
278 $ cat <<EOF > $TESTTMP/badpyhook.py
278 $ cat <<EOF > $TESTTMP/badpyhook.py
279 > import sys
279 > import sys
280 > def hook(ui, repo, hooktype, **kwargs):
280 > def hook(ui, repo, hooktype, **kwargs):
281 > sys.stdout.write("KABOOM IN PROCESS\n")
281 > sys.stdout.write("KABOOM IN PROCESS\n")
282 > EOF
282 > EOF
283
283
284 $ cat <<EOF >> ../remote/.hg/hgrc
284 $ cat <<EOF >> ../remote/.hg/hgrc
285 > [hooks]
285 > [hooks]
286 > changegroup.stdout = $PYTHON $TESTTMP/badhook
286 > changegroup.stdout = $PYTHON $TESTTMP/badhook
287 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
287 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
288 > EOF
288 > EOF
289 $ echo r > r
289 $ echo r > r
290 $ hg ci -A -m z r
290 $ hg ci -A -m z r
291
291
292 push should succeed even though it has an unexpected response
292 push should succeed even though it has an unexpected response
293
293
294 $ hg push
294 $ hg push
295 pushing to ssh://user@dummy/remote
295 pushing to ssh://user@dummy/remote
296 searching for changes
296 searching for changes
297 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
297 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
298 remote: adding changesets
298 remote: adding changesets
299 remote: adding manifests
299 remote: adding manifests
300 remote: adding file changes
300 remote: adding file changes
301 remote: added 1 changesets with 1 changes to 1 files
301 remote: added 1 changesets with 1 changes to 1 files
302 remote: KABOOM
302 remote: KABOOM
303 remote: KABOOM IN PROCESS
303 remote: KABOOM IN PROCESS
304 $ hg -R ../remote heads
304 $ hg -R ../remote heads
305 changeset: 5:1383141674ec
305 changeset: 5:1383141674ec
306 tag: tip
306 tag: tip
307 parent: 3:a28a9d1a809c
307 parent: 3:a28a9d1a809c
308 user: test
308 user: test
309 date: Thu Jan 01 00:00:00 1970 +0000
309 date: Thu Jan 01 00:00:00 1970 +0000
310 summary: z
310 summary: z
311
311
312 changeset: 4:6c0482d977a3
312 changeset: 4:6c0482d977a3
313 parent: 0:1160648e36ce
313 parent: 0:1160648e36ce
314 user: test
314 user: test
315 date: Thu Jan 01 00:00:00 1970 +0000
315 date: Thu Jan 01 00:00:00 1970 +0000
316 summary: z
316 summary: z
317
317
318
318
319 clone bookmarks
319 clone bookmarks
320
320
321 $ hg -R ../remote bookmark test
321 $ hg -R ../remote bookmark test
322 $ hg -R ../remote bookmarks
322 $ hg -R ../remote bookmarks
323 * test 4:6c0482d977a3
323 * test 4:6c0482d977a3
324 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
324 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
325 requesting all changes
325 requesting all changes
326 adding changesets
326 adding changesets
327 adding manifests
327 adding manifests
328 adding file changes
328 adding file changes
329 added 6 changesets with 5 changes to 4 files (+1 heads)
329 added 6 changesets with 5 changes to 4 files (+1 heads)
330 new changesets 1160648e36ce:1383141674ec
330 new changesets 1160648e36ce:1383141674ec
331 updating to branch default
331 updating to branch default
332 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
333 $ hg -R local-bookmarks bookmarks
333 $ hg -R local-bookmarks bookmarks
334 test 4:6c0482d977a3
334 test 4:6c0482d977a3
335
335
336 passwords in ssh urls are not supported
336 passwords in ssh urls are not supported
337 (we use a glob here because different Python versions give different
337 (we use a glob here because different Python versions give different
338 results here)
338 results here)
339
339
340 $ hg push ssh://user:erroneouspwd@dummy/remote
340 $ hg push ssh://user:erroneouspwd@dummy/remote
341 pushing to ssh://user:*@dummy/remote (glob)
341 pushing to ssh://user:*@dummy/remote (glob)
342 abort: password in URL not supported!
342 abort: password in URL not supported!
343 [255]
343 [255]
344
344
345 $ cd ..
345 $ cd ..
346
346
347 hide outer repo
347 hide outer repo
348 $ hg init
348 $ hg init
349
349
350 Test remote paths with spaces (issue2983):
350 Test remote paths with spaces (issue2983):
351
351
352 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
352 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
353 $ touch "$TESTTMP/a repo/test"
353 $ touch "$TESTTMP/a repo/test"
354 $ hg -R 'a repo' commit -A -m "test"
354 $ hg -R 'a repo' commit -A -m "test"
355 adding test
355 adding test
356 $ hg -R 'a repo' tag tag
356 $ hg -R 'a repo' tag tag
357 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
357 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
358 73649e48688a
358 73649e48688a
359
359
360 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
360 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
361 abort: unknown revision 'noNoNO'!
361 abort: unknown revision 'noNoNO'!
362 [255]
362 [255]
363
363
364 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
364 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
365
365
366 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
366 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
367 destination directory: a repo
367 destination directory: a repo
368 abort: destination 'a repo' is not empty
368 abort: destination 'a repo' is not empty
369 [255]
369 [255]
370
370
371 Make sure hg is really paranoid in serve --stdio mode. It used to be
371 Make sure hg is really paranoid in serve --stdio mode. It used to be
372 possible to get a debugger REPL by specifying a repo named --debugger.
372 possible to get a debugger REPL by specifying a repo named --debugger.
373 $ hg -R --debugger serve --stdio
373 $ hg -R --debugger serve --stdio
374 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
374 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
375 [255]
375 [255]
376 $ hg -R --config=ui.debugger=yes serve --stdio
376 $ hg -R --config=ui.debugger=yes serve --stdio
377 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
377 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
378 [255]
378 [255]
379 Abbreviations of 'serve' also don't work, to avoid shenanigans.
379 Abbreviations of 'serve' also don't work, to avoid shenanigans.
380 $ hg -R narf serv --stdio
380 $ hg -R narf serv --stdio
381 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
381 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
382 [255]
382 [255]
383
383
384 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
384 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
385 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
385 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
386 parameters:
386 parameters:
387
387
388 $ cat > ssh.sh << EOF
388 $ cat > ssh.sh << EOF
389 > userhost="\$1"
389 > userhost="\$1"
390 > SSH_ORIGINAL_COMMAND="\$2"
390 > SSH_ORIGINAL_COMMAND="\$2"
391 > export SSH_ORIGINAL_COMMAND
391 > export SSH_ORIGINAL_COMMAND
392 > PYTHONPATH="$PYTHONPATH"
392 > PYTHONPATH="$PYTHONPATH"
393 > export PYTHONPATH
393 > export PYTHONPATH
394 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
394 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
395 > EOF
395 > EOF
396
396
397 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
397 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
398 73649e48688a
398 73649e48688a
399
399
400 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
400 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
401 remote: Illegal repository "$TESTTMP/a'repo"
401 remote: Illegal repository "$TESTTMP/a'repo"
402 abort: no suitable response from remote hg!
402 abort: no suitable response from remote hg!
403 [255]
403 [255]
404
404
405 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
405 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
406 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
406 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
407 abort: no suitable response from remote hg!
407 abort: no suitable response from remote hg!
408 [255]
408 [255]
409
409
410 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
410 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
411 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
411 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
412 [255]
412 [255]
413
413
414 Test hg-ssh in read-only mode:
414 Test hg-ssh in read-only mode:
415
415
416 $ cat > ssh.sh << EOF
416 $ cat > ssh.sh << EOF
417 > userhost="\$1"
417 > userhost="\$1"
418 > SSH_ORIGINAL_COMMAND="\$2"
418 > SSH_ORIGINAL_COMMAND="\$2"
419 > export SSH_ORIGINAL_COMMAND
419 > export SSH_ORIGINAL_COMMAND
420 > PYTHONPATH="$PYTHONPATH"
420 > PYTHONPATH="$PYTHONPATH"
421 > export PYTHONPATH
421 > export PYTHONPATH
422 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
422 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
423 > EOF
423 > EOF
424
424
425 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
425 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
426 requesting all changes
426 requesting all changes
427 adding changesets
427 adding changesets
428 adding manifests
428 adding manifests
429 adding file changes
429 adding file changes
430 added 6 changesets with 5 changes to 4 files (+1 heads)
430 added 6 changesets with 5 changes to 4 files (+1 heads)
431 new changesets 1160648e36ce:1383141674ec
431 new changesets 1160648e36ce:1383141674ec
432 updating to branch default
432 updating to branch default
433 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
434
434
435 $ cd read-only-local
435 $ cd read-only-local
436 $ echo "baz" > bar
436 $ echo "baz" > bar
437 $ hg ci -A -m "unpushable commit" bar
437 $ hg ci -A -m "unpushable commit" bar
438 $ hg push --ssh "sh ../ssh.sh"
438 $ hg push --ssh "sh ../ssh.sh"
439 pushing to ssh://user@dummy/*/remote (glob)
439 pushing to ssh://user@dummy/*/remote (glob)
440 searching for changes
440 searching for changes
441 remote: Permission denied
441 remote: Permission denied
442 remote: pretxnopen.hg-ssh hook failed
442 remote: pretxnopen.hg-ssh hook failed
443 abort: push failed on remote
443 abort: push failed on remote
444 [255]
444 [255]
445
445
446 $ cd ..
446 $ cd ..
447
447
448 stderr from remote commands should be printed before stdout from local code (issue4336)
448 stderr from remote commands should be printed before stdout from local code (issue4336)
449
449
450 $ hg clone remote stderr-ordering
450 $ hg clone remote stderr-ordering
451 updating to branch default
451 updating to branch default
452 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
452 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 $ cd stderr-ordering
453 $ cd stderr-ordering
454 $ cat >> localwrite.py << EOF
454 $ cat >> localwrite.py << EOF
455 > from mercurial import exchange, extensions
455 > from mercurial import exchange, extensions
456 >
456 >
457 > def wrappedpush(orig, repo, *args, **kwargs):
457 > def wrappedpush(orig, repo, *args, **kwargs):
458 > res = orig(repo, *args, **kwargs)
458 > res = orig(repo, *args, **kwargs)
459 > repo.ui.write('local stdout\n')
459 > repo.ui.write('local stdout\n')
460 > return res
460 > return res
461 >
461 >
462 > def extsetup(ui):
462 > def extsetup(ui):
463 > extensions.wrapfunction(exchange, 'push', wrappedpush)
463 > extensions.wrapfunction(exchange, 'push', wrappedpush)
464 > EOF
464 > EOF
465
465
466 $ cat >> .hg/hgrc << EOF
466 $ cat >> .hg/hgrc << EOF
467 > [paths]
467 > [paths]
468 > default-push = ssh://user@dummy/remote
468 > default-push = ssh://user@dummy/remote
469 > [ui]
469 > [ui]
470 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
470 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
471 > [extensions]
471 > [extensions]
472 > localwrite = localwrite.py
472 > localwrite = localwrite.py
473 > EOF
473 > EOF
474
474
475 $ echo localwrite > foo
475 $ echo localwrite > foo
476 $ hg commit -m 'testing localwrite'
476 $ hg commit -m 'testing localwrite'
477 $ hg push
477 $ hg push
478 pushing to ssh://user@dummy/remote
478 pushing to ssh://user@dummy/remote
479 searching for changes
479 searching for changes
480 remote: adding changesets
480 remote: adding changesets
481 remote: adding manifests
481 remote: adding manifests
482 remote: adding file changes
482 remote: adding file changes
483 remote: added 1 changesets with 1 changes to 1 files
483 remote: added 1 changesets with 1 changes to 1 files
484 remote: KABOOM
484 remote: KABOOM
485 remote: KABOOM IN PROCESS
485 remote: KABOOM IN PROCESS
486 local stdout
486 local stdout
487
487
488 debug output
488 debug output
489
489
490 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
490 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
491 pulling from ssh://user@dummy/remote
491 pulling from ssh://user@dummy/remote
492 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
492 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
493 sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
493 sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
494 devel-peer-request: hello
494 devel-peer-request: hello
495 sending hello command
495 sending hello command
496 devel-peer-request: between
496 devel-peer-request: between
497 devel-peer-request: pairs: 81 bytes
497 devel-peer-request: pairs: 81 bytes
498 sending between command
498 sending between command
499 remote: 384 (sshv1 !)
499 remote: 384 (sshv1 !)
500 protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
500 protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
501 remote: capabilities: lookup branchmap pushkey known getbundle unbundlehash batch changegroupsubset streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
501 remote: capabilities: lookup branchmap pushkey known getbundle unbundlehash batch changegroupsubset streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
502 remote: 1 (sshv1 !)
502 remote: 1 (sshv1 !)
503 query 1; heads
503 query 1; heads
504 devel-peer-request: batched-content
505 devel-peer-request: - heads (0 arguments)
506 devel-peer-request: - known (1 arguments)
504 devel-peer-request: batch
507 devel-peer-request: batch
505 devel-peer-request: cmds: 141 bytes
508 devel-peer-request: cmds: 141 bytes
506 sending batch command
509 sending batch command
507 searching for changes
510 searching for changes
508 all remote heads known locally
511 all remote heads known locally
509 no changes found
512 no changes found
510 devel-peer-request: getbundle
513 devel-peer-request: getbundle
511 devel-peer-request: bookmarks: 1 bytes
514 devel-peer-request: bookmarks: 1 bytes
512 devel-peer-request: bundlecaps: 247 bytes
515 devel-peer-request: bundlecaps: 247 bytes
513 devel-peer-request: cg: 1 bytes
516 devel-peer-request: cg: 1 bytes
514 devel-peer-request: common: 122 bytes
517 devel-peer-request: common: 122 bytes
515 devel-peer-request: heads: 122 bytes
518 devel-peer-request: heads: 122 bytes
516 devel-peer-request: listkeys: 9 bytes
519 devel-peer-request: listkeys: 9 bytes
517 devel-peer-request: phases: 1 bytes
520 devel-peer-request: phases: 1 bytes
518 sending getbundle command
521 sending getbundle command
519 bundle2-input-bundle: with-transaction
522 bundle2-input-bundle: with-transaction
520 bundle2-input-part: "bookmarks" supported
523 bundle2-input-part: "bookmarks" supported
521 bundle2-input-part: total payload size 26
524 bundle2-input-part: total payload size 26
522 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
525 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
523 bundle2-input-part: total payload size 45
526 bundle2-input-part: total payload size 45
524 bundle2-input-part: "phase-heads" supported
527 bundle2-input-part: "phase-heads" supported
525 bundle2-input-part: total payload size 72
528 bundle2-input-part: total payload size 72
526 bundle2-input-bundle: 2 parts total
529 bundle2-input-bundle: 2 parts total
527 checking for updated bookmarks
530 checking for updated bookmarks
528
531
529 $ cd ..
532 $ cd ..
530
533
531 $ cat dummylog
534 $ cat dummylog
532 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
535 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
533 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
536 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
534 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
535 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
538 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
536 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
538 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
541 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
542 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 Got arguments 1:user@dummy 2:hg -R local serve --stdio
543 Got arguments 1:user@dummy 2:hg -R local serve --stdio
541 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
544 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
542 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
545 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
543 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
546 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
544 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
547 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
545 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
548 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
546 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
549 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
547 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
550 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
548 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
551 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
549 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
552 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
550 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
553 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
551 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
554 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
552 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
555 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
553 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
556 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
554 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
557 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
555 Got arguments 1:user@dummy 2:hg init 'a repo'
558 Got arguments 1:user@dummy 2:hg init 'a repo'
556 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
559 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
557 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
560 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
558 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
561 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
559 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
562 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
560 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
563 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
561 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
564 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
562 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
565 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
563
566
564 remote hook failure is attributed to remote
567 remote hook failure is attributed to remote
565
568
566 $ cat > $TESTTMP/failhook << EOF
569 $ cat > $TESTTMP/failhook << EOF
567 > def hook(ui, repo, **kwargs):
570 > def hook(ui, repo, **kwargs):
568 > ui.write('hook failure!\n')
571 > ui.write('hook failure!\n')
569 > ui.flush()
572 > ui.flush()
570 > return 1
573 > return 1
571 > EOF
574 > EOF
572
575
573 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
576 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
574
577
575 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
578 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
576 $ cd hookout
579 $ cd hookout
577 $ touch hookfailure
580 $ touch hookfailure
578 $ hg -q commit -A -m 'remote hook failure'
581 $ hg -q commit -A -m 'remote hook failure'
579 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
582 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
580 pushing to ssh://user@dummy/remote
583 pushing to ssh://user@dummy/remote
581 searching for changes
584 searching for changes
582 remote: adding changesets
585 remote: adding changesets
583 remote: adding manifests
586 remote: adding manifests
584 remote: adding file changes
587 remote: adding file changes
585 remote: added 1 changesets with 1 changes to 1 files
588 remote: added 1 changesets with 1 changes to 1 files
586 remote: hook failure!
589 remote: hook failure!
587 remote: transaction abort!
590 remote: transaction abort!
588 remote: rollback completed
591 remote: rollback completed
589 remote: pretxnchangegroup.fail hook failed
592 remote: pretxnchangegroup.fail hook failed
590 abort: push failed on remote
593 abort: push failed on remote
591 [255]
594 [255]
592
595
593 abort during pull is properly reported as such
596 abort during pull is properly reported as such
594
597
595 $ echo morefoo >> ../remote/foo
598 $ echo morefoo >> ../remote/foo
596 $ hg -R ../remote commit --message "more foo to be pulled"
599 $ hg -R ../remote commit --message "more foo to be pulled"
597 $ cat >> ../remote/.hg/hgrc << EOF
600 $ cat >> ../remote/.hg/hgrc << EOF
598 > [extensions]
601 > [extensions]
599 > crash = ${TESTDIR}/crashgetbundler.py
602 > crash = ${TESTDIR}/crashgetbundler.py
600 > EOF
603 > EOF
601 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
604 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
602 pulling from ssh://user@dummy/remote
605 pulling from ssh://user@dummy/remote
603 searching for changes
606 searching for changes
604 remote: abort: this is an exercise
607 remote: abort: this is an exercise
605 abort: pull failed on remote
608 abort: pull failed on remote
606 [255]
609 [255]
607
610
608 abort with no error hint when there is a ssh problem when pulling
611 abort with no error hint when there is a ssh problem when pulling
609
612
610 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
613 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
611 pulling from ssh://brokenrepository/
614 pulling from ssh://brokenrepository/
612 abort: no suitable response from remote hg!
615 abort: no suitable response from remote hg!
613 [255]
616 [255]
614
617
615 abort with configured error hint when there is a ssh problem when pulling
618 abort with configured error hint when there is a ssh problem when pulling
616
619
617 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
620 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
618 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
621 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
619 pulling from ssh://brokenrepository/
622 pulling from ssh://brokenrepository/
620 abort: no suitable response from remote hg!
623 abort: no suitable response from remote hg!
621 (Please see http://company/internalwiki/ssh.html)
624 (Please see http://company/internalwiki/ssh.html)
622 [255]
625 [255]
623
626
624 test that custom environment is passed down to ssh executable
627 test that custom environment is passed down to ssh executable
625 $ cat >>dumpenv <<EOF
628 $ cat >>dumpenv <<EOF
626 > #! /bin/sh
629 > #! /bin/sh
627 > echo \$VAR >&2
630 > echo \$VAR >&2
628 > EOF
631 > EOF
629 $ chmod +x dumpenv
632 $ chmod +x dumpenv
630 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
633 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
631 pulling from ssh://something/
634 pulling from ssh://something/
632 remote:
635 remote:
633 abort: no suitable response from remote hg!
636 abort: no suitable response from remote hg!
634 [255]
637 [255]
635 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
638 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
636 pulling from ssh://something/
639 pulling from ssh://something/
637 remote: 17
640 remote: 17
638 abort: no suitable response from remote hg!
641 abort: no suitable response from remote hg!
639 [255]
642 [255]
640
643
General Comments 0
You need to be logged in to leave comments. Login now