##// END OF EJS Templates
wireproto: only advertise HTTP-specific capabilities to HTTP peers (BC)...
Gregory Szorc -
r30563:e1182331 default
parent child Browse files
Show More
@@ -1,958 +1,961 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 itertools
11 import itertools
12 import os
12 import os
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup as changegroupmod,
23 changegroup as changegroupmod,
24 encoding,
24 encoding,
25 error,
25 error,
26 exchange,
26 exchange,
27 peer,
27 peer,
28 pushkey as pushkeymod,
28 pushkey as pushkeymod,
29 streamclone,
29 streamclone,
30 util,
30 util,
31 )
31 )
32
32
33 urlerr = util.urlerr
33 urlerr = util.urlerr
34 urlreq = util.urlreq
34 urlreq = util.urlreq
35
35
36 bundle2required = _(
36 bundle2required = _(
37 'incompatible Mercurial client; bundle2 required\n'
37 'incompatible Mercurial client; bundle2 required\n'
38 '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
38 '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
39
39
40 class abstractserverproto(object):
40 class abstractserverproto(object):
41 """abstract class that summarizes the protocol API
41 """abstract class that summarizes the protocol API
42
42
43 Used as reference and documentation.
43 Used as reference and documentation.
44 """
44 """
45
45
46 def getargs(self, args):
46 def getargs(self, args):
47 """return the value for arguments in <args>
47 """return the value for arguments in <args>
48
48
49 returns a list of values (same order as <args>)"""
49 returns a list of values (same order as <args>)"""
50 raise NotImplementedError()
50 raise NotImplementedError()
51
51
52 def getfile(self, fp):
52 def getfile(self, fp):
53 """write the whole content of a file into a file like object
53 """write the whole content of a file into a file like object
54
54
55 The file is in the form::
55 The file is in the form::
56
56
57 (<chunk-size>\n<chunk>)+0\n
57 (<chunk-size>\n<chunk>)+0\n
58
58
59 chunk size is the ascii version of the int.
59 chunk size is the ascii version of the int.
60 """
60 """
61 raise NotImplementedError()
61 raise NotImplementedError()
62
62
63 def redirect(self):
63 def redirect(self):
64 """may setup interception for stdout and stderr
64 """may setup interception for stdout and stderr
65
65
66 See also the `restore` method."""
66 See also the `restore` method."""
67 raise NotImplementedError()
67 raise NotImplementedError()
68
68
69 # If the `redirect` function does install interception, the `restore`
69 # If the `redirect` function does install interception, the `restore`
70 # function MUST be defined. If interception is not used, this function
70 # function MUST be defined. If interception is not used, this function
71 # MUST NOT be defined.
71 # MUST NOT be defined.
72 #
72 #
73 # left commented here on purpose
73 # left commented here on purpose
74 #
74 #
75 #def restore(self):
75 #def restore(self):
76 # """reinstall previous stdout and stderr and return intercepted stdout
76 # """reinstall previous stdout and stderr and return intercepted stdout
77 # """
77 # """
78 # raise NotImplementedError()
78 # raise NotImplementedError()
79
79
80 class remotebatch(peer.batcher):
80 class remotebatch(peer.batcher):
81 '''batches the queued calls; uses as few roundtrips as possible'''
81 '''batches the queued calls; uses as few roundtrips as possible'''
82 def __init__(self, remote):
82 def __init__(self, remote):
83 '''remote must support _submitbatch(encbatch) and
83 '''remote must support _submitbatch(encbatch) and
84 _submitone(op, encargs)'''
84 _submitone(op, encargs)'''
85 peer.batcher.__init__(self)
85 peer.batcher.__init__(self)
86 self.remote = remote
86 self.remote = remote
87 def submit(self):
87 def submit(self):
88 req, rsp = [], []
88 req, rsp = [], []
89 for name, args, opts, resref in self.calls:
89 for name, args, opts, resref in self.calls:
90 mtd = getattr(self.remote, name)
90 mtd = getattr(self.remote, name)
91 batchablefn = getattr(mtd, 'batchable', None)
91 batchablefn = getattr(mtd, 'batchable', None)
92 if batchablefn is not None:
92 if batchablefn is not None:
93 batchable = batchablefn(mtd.im_self, *args, **opts)
93 batchable = batchablefn(mtd.im_self, *args, **opts)
94 encargsorres, encresref = next(batchable)
94 encargsorres, encresref = next(batchable)
95 if encresref:
95 if encresref:
96 req.append((name, encargsorres,))
96 req.append((name, encargsorres,))
97 rsp.append((batchable, encresref, resref,))
97 rsp.append((batchable, encresref, resref,))
98 else:
98 else:
99 resref.set(encargsorres)
99 resref.set(encargsorres)
100 else:
100 else:
101 if req:
101 if req:
102 self._submitreq(req, rsp)
102 self._submitreq(req, rsp)
103 req, rsp = [], []
103 req, rsp = [], []
104 resref.set(mtd(*args, **opts))
104 resref.set(mtd(*args, **opts))
105 if req:
105 if req:
106 self._submitreq(req, rsp)
106 self._submitreq(req, rsp)
107 def _submitreq(self, req, rsp):
107 def _submitreq(self, req, rsp):
108 encresults = self.remote._submitbatch(req)
108 encresults = self.remote._submitbatch(req)
109 for encres, r in zip(encresults, rsp):
109 for encres, r in zip(encresults, rsp):
110 batchable, encresref, resref = r
110 batchable, encresref, resref = r
111 encresref.set(encres)
111 encresref.set(encres)
112 resref.set(next(batchable))
112 resref.set(next(batchable))
113
113
114 class remoteiterbatcher(peer.iterbatcher):
114 class remoteiterbatcher(peer.iterbatcher):
115 def __init__(self, remote):
115 def __init__(self, remote):
116 super(remoteiterbatcher, self).__init__()
116 super(remoteiterbatcher, self).__init__()
117 self._remote = remote
117 self._remote = remote
118
118
119 def __getattr__(self, name):
119 def __getattr__(self, name):
120 if not getattr(self._remote, name, False):
120 if not getattr(self._remote, name, False):
121 raise AttributeError(
121 raise AttributeError(
122 'Attempted to iterbatch non-batchable call to %r' % name)
122 'Attempted to iterbatch non-batchable call to %r' % name)
123 return super(remoteiterbatcher, self).__getattr__(name)
123 return super(remoteiterbatcher, self).__getattr__(name)
124
124
125 def submit(self):
125 def submit(self):
126 """Break the batch request into many patch calls and pipeline them.
126 """Break the batch request into many patch calls and pipeline them.
127
127
128 This is mostly valuable over http where request sizes can be
128 This is mostly valuable over http where request sizes can be
129 limited, but can be used in other places as well.
129 limited, but can be used in other places as well.
130 """
130 """
131 req, rsp = [], []
131 req, rsp = [], []
132 for name, args, opts, resref in self.calls:
132 for name, args, opts, resref in self.calls:
133 mtd = getattr(self._remote, name)
133 mtd = getattr(self._remote, name)
134 batchable = mtd.batchable(mtd.im_self, *args, **opts)
134 batchable = mtd.batchable(mtd.im_self, *args, **opts)
135 encargsorres, encresref = next(batchable)
135 encargsorres, encresref = next(batchable)
136 assert encresref
136 assert encresref
137 req.append((name, encargsorres))
137 req.append((name, encargsorres))
138 rsp.append((batchable, encresref))
138 rsp.append((batchable, encresref))
139 if req:
139 if req:
140 self._resultiter = self._remote._submitbatch(req)
140 self._resultiter = self._remote._submitbatch(req)
141 self._rsp = rsp
141 self._rsp = rsp
142
142
143 def results(self):
143 def results(self):
144 for (batchable, encresref), encres in itertools.izip(
144 for (batchable, encresref), encres in itertools.izip(
145 self._rsp, self._resultiter):
145 self._rsp, self._resultiter):
146 encresref.set(encres)
146 encresref.set(encres)
147 yield next(batchable)
147 yield next(batchable)
148
148
149 # Forward a couple of names from peer to make wireproto interactions
149 # Forward a couple of names from peer to make wireproto interactions
150 # slightly more sensible.
150 # slightly more sensible.
151 batchable = peer.batchable
151 batchable = peer.batchable
152 future = peer.future
152 future = peer.future
153
153
154 # list of nodes encoding / decoding
154 # list of nodes encoding / decoding
155
155
156 def decodelist(l, sep=' '):
156 def decodelist(l, sep=' '):
157 if l:
157 if l:
158 return map(bin, l.split(sep))
158 return map(bin, l.split(sep))
159 return []
159 return []
160
160
161 def encodelist(l, sep=' '):
161 def encodelist(l, sep=' '):
162 try:
162 try:
163 return sep.join(map(hex, l))
163 return sep.join(map(hex, l))
164 except TypeError:
164 except TypeError:
165 raise
165 raise
166
166
167 # batched call argument encoding
167 # batched call argument encoding
168
168
169 def escapearg(plain):
169 def escapearg(plain):
170 return (plain
170 return (plain
171 .replace(':', ':c')
171 .replace(':', ':c')
172 .replace(',', ':o')
172 .replace(',', ':o')
173 .replace(';', ':s')
173 .replace(';', ':s')
174 .replace('=', ':e'))
174 .replace('=', ':e'))
175
175
176 def unescapearg(escaped):
176 def unescapearg(escaped):
177 return (escaped
177 return (escaped
178 .replace(':e', '=')
178 .replace(':e', '=')
179 .replace(':s', ';')
179 .replace(':s', ';')
180 .replace(':o', ',')
180 .replace(':o', ',')
181 .replace(':c', ':'))
181 .replace(':c', ':'))
182
182
183 def encodebatchcmds(req):
183 def encodebatchcmds(req):
184 """Return a ``cmds`` argument value for the ``batch`` command."""
184 """Return a ``cmds`` argument value for the ``batch`` command."""
185 cmds = []
185 cmds = []
186 for op, argsdict in req:
186 for op, argsdict in req:
187 # Old servers didn't properly unescape argument names. So prevent
187 # Old servers didn't properly unescape argument names. So prevent
188 # the sending of argument names that may not be decoded properly by
188 # the sending of argument names that may not be decoded properly by
189 # servers.
189 # servers.
190 assert all(escapearg(k) == k for k in argsdict)
190 assert all(escapearg(k) == k for k in argsdict)
191
191
192 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
192 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
193 for k, v in argsdict.iteritems())
193 for k, v in argsdict.iteritems())
194 cmds.append('%s %s' % (op, args))
194 cmds.append('%s %s' % (op, args))
195
195
196 return ';'.join(cmds)
196 return ';'.join(cmds)
197
197
198 # mapping of options accepted by getbundle and their types
198 # mapping of options accepted by getbundle and their types
199 #
199 #
200 # Meant to be extended by extensions. It is extensions responsibility to ensure
200 # Meant to be extended by extensions. It is extensions responsibility to ensure
201 # such options are properly processed in exchange.getbundle.
201 # such options are properly processed in exchange.getbundle.
202 #
202 #
203 # supported types are:
203 # supported types are:
204 #
204 #
205 # :nodes: list of binary nodes
205 # :nodes: list of binary nodes
206 # :csv: list of comma-separated values
206 # :csv: list of comma-separated values
207 # :scsv: list of comma-separated values return as set
207 # :scsv: list of comma-separated values return as set
208 # :plain: string with no transformation needed.
208 # :plain: string with no transformation needed.
209 gboptsmap = {'heads': 'nodes',
209 gboptsmap = {'heads': 'nodes',
210 'common': 'nodes',
210 'common': 'nodes',
211 'obsmarkers': 'boolean',
211 'obsmarkers': 'boolean',
212 'bundlecaps': 'scsv',
212 'bundlecaps': 'scsv',
213 'listkeys': 'csv',
213 'listkeys': 'csv',
214 'cg': 'boolean',
214 'cg': 'boolean',
215 'cbattempted': 'boolean'}
215 'cbattempted': 'boolean'}
216
216
217 # client side
217 # client side
218
218
219 class wirepeer(peer.peerrepository):
219 class wirepeer(peer.peerrepository):
220 """Client-side interface for communicating with a peer repository.
220 """Client-side interface for communicating with a peer repository.
221
221
222 Methods commonly call wire protocol commands of the same name.
222 Methods commonly call wire protocol commands of the same name.
223
223
224 See also httppeer.py and sshpeer.py for protocol-specific
224 See also httppeer.py and sshpeer.py for protocol-specific
225 implementations of this interface.
225 implementations of this interface.
226 """
226 """
227 def batch(self):
227 def batch(self):
228 if self.capable('batch'):
228 if self.capable('batch'):
229 return remotebatch(self)
229 return remotebatch(self)
230 else:
230 else:
231 return peer.localbatch(self)
231 return peer.localbatch(self)
232 def _submitbatch(self, req):
232 def _submitbatch(self, req):
233 """run batch request <req> on the server
233 """run batch request <req> on the server
234
234
235 Returns an iterator of the raw responses from the server.
235 Returns an iterator of the raw responses from the server.
236 """
236 """
237 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
237 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
238 chunk = rsp.read(1024)
238 chunk = rsp.read(1024)
239 work = [chunk]
239 work = [chunk]
240 while chunk:
240 while chunk:
241 while ';' not in chunk and chunk:
241 while ';' not in chunk and chunk:
242 chunk = rsp.read(1024)
242 chunk = rsp.read(1024)
243 work.append(chunk)
243 work.append(chunk)
244 merged = ''.join(work)
244 merged = ''.join(work)
245 while ';' in merged:
245 while ';' in merged:
246 one, merged = merged.split(';', 1)
246 one, merged = merged.split(';', 1)
247 yield unescapearg(one)
247 yield unescapearg(one)
248 chunk = rsp.read(1024)
248 chunk = rsp.read(1024)
249 work = [merged, chunk]
249 work = [merged, chunk]
250 yield unescapearg(''.join(work))
250 yield unescapearg(''.join(work))
251
251
252 def _submitone(self, op, args):
252 def _submitone(self, op, args):
253 return self._call(op, **args)
253 return self._call(op, **args)
254
254
255 def iterbatch(self):
255 def iterbatch(self):
256 return remoteiterbatcher(self)
256 return remoteiterbatcher(self)
257
257
258 @batchable
258 @batchable
259 def lookup(self, key):
259 def lookup(self, key):
260 self.requirecap('lookup', _('look up remote revision'))
260 self.requirecap('lookup', _('look up remote revision'))
261 f = future()
261 f = future()
262 yield {'key': encoding.fromlocal(key)}, f
262 yield {'key': encoding.fromlocal(key)}, f
263 d = f.value
263 d = f.value
264 success, data = d[:-1].split(" ", 1)
264 success, data = d[:-1].split(" ", 1)
265 if int(success):
265 if int(success):
266 yield bin(data)
266 yield bin(data)
267 self._abort(error.RepoError(data))
267 self._abort(error.RepoError(data))
268
268
269 @batchable
269 @batchable
270 def heads(self):
270 def heads(self):
271 f = future()
271 f = future()
272 yield {}, f
272 yield {}, f
273 d = f.value
273 d = f.value
274 try:
274 try:
275 yield decodelist(d[:-1])
275 yield decodelist(d[:-1])
276 except ValueError:
276 except ValueError:
277 self._abort(error.ResponseError(_("unexpected response:"), d))
277 self._abort(error.ResponseError(_("unexpected response:"), d))
278
278
279 @batchable
279 @batchable
280 def known(self, nodes):
280 def known(self, nodes):
281 f = future()
281 f = future()
282 yield {'nodes': encodelist(nodes)}, f
282 yield {'nodes': encodelist(nodes)}, f
283 d = f.value
283 d = f.value
284 try:
284 try:
285 yield [bool(int(b)) for b in d]
285 yield [bool(int(b)) for b in d]
286 except ValueError:
286 except ValueError:
287 self._abort(error.ResponseError(_("unexpected response:"), d))
287 self._abort(error.ResponseError(_("unexpected response:"), d))
288
288
289 @batchable
289 @batchable
290 def branchmap(self):
290 def branchmap(self):
291 f = future()
291 f = future()
292 yield {}, f
292 yield {}, f
293 d = f.value
293 d = f.value
294 try:
294 try:
295 branchmap = {}
295 branchmap = {}
296 for branchpart in d.splitlines():
296 for branchpart in d.splitlines():
297 branchname, branchheads = branchpart.split(' ', 1)
297 branchname, branchheads = branchpart.split(' ', 1)
298 branchname = encoding.tolocal(urlreq.unquote(branchname))
298 branchname = encoding.tolocal(urlreq.unquote(branchname))
299 branchheads = decodelist(branchheads)
299 branchheads = decodelist(branchheads)
300 branchmap[branchname] = branchheads
300 branchmap[branchname] = branchheads
301 yield branchmap
301 yield branchmap
302 except TypeError:
302 except TypeError:
303 self._abort(error.ResponseError(_("unexpected response:"), d))
303 self._abort(error.ResponseError(_("unexpected response:"), d))
304
304
305 def branches(self, nodes):
305 def branches(self, nodes):
306 n = encodelist(nodes)
306 n = encodelist(nodes)
307 d = self._call("branches", nodes=n)
307 d = self._call("branches", nodes=n)
308 try:
308 try:
309 br = [tuple(decodelist(b)) for b in d.splitlines()]
309 br = [tuple(decodelist(b)) for b in d.splitlines()]
310 return br
310 return br
311 except ValueError:
311 except ValueError:
312 self._abort(error.ResponseError(_("unexpected response:"), d))
312 self._abort(error.ResponseError(_("unexpected response:"), d))
313
313
314 def between(self, pairs):
314 def between(self, pairs):
315 batch = 8 # avoid giant requests
315 batch = 8 # avoid giant requests
316 r = []
316 r = []
317 for i in xrange(0, len(pairs), batch):
317 for i in xrange(0, len(pairs), batch):
318 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
318 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
319 d = self._call("between", pairs=n)
319 d = self._call("between", pairs=n)
320 try:
320 try:
321 r.extend(l and decodelist(l) or [] for l in d.splitlines())
321 r.extend(l and decodelist(l) or [] for l in d.splitlines())
322 except ValueError:
322 except ValueError:
323 self._abort(error.ResponseError(_("unexpected response:"), d))
323 self._abort(error.ResponseError(_("unexpected response:"), d))
324 return r
324 return r
325
325
326 @batchable
326 @batchable
327 def pushkey(self, namespace, key, old, new):
327 def pushkey(self, namespace, key, old, new):
328 if not self.capable('pushkey'):
328 if not self.capable('pushkey'):
329 yield False, None
329 yield False, None
330 f = future()
330 f = future()
331 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
331 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
332 yield {'namespace': encoding.fromlocal(namespace),
332 yield {'namespace': encoding.fromlocal(namespace),
333 'key': encoding.fromlocal(key),
333 'key': encoding.fromlocal(key),
334 'old': encoding.fromlocal(old),
334 'old': encoding.fromlocal(old),
335 'new': encoding.fromlocal(new)}, f
335 'new': encoding.fromlocal(new)}, f
336 d = f.value
336 d = f.value
337 d, output = d.split('\n', 1)
337 d, output = d.split('\n', 1)
338 try:
338 try:
339 d = bool(int(d))
339 d = bool(int(d))
340 except ValueError:
340 except ValueError:
341 raise error.ResponseError(
341 raise error.ResponseError(
342 _('push failed (unexpected response):'), d)
342 _('push failed (unexpected response):'), d)
343 for l in output.splitlines(True):
343 for l in output.splitlines(True):
344 self.ui.status(_('remote: '), l)
344 self.ui.status(_('remote: '), l)
345 yield d
345 yield d
346
346
347 @batchable
347 @batchable
348 def listkeys(self, namespace):
348 def listkeys(self, namespace):
349 if not self.capable('pushkey'):
349 if not self.capable('pushkey'):
350 yield {}, None
350 yield {}, None
351 f = future()
351 f = future()
352 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
352 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
353 yield {'namespace': encoding.fromlocal(namespace)}, f
353 yield {'namespace': encoding.fromlocal(namespace)}, f
354 d = f.value
354 d = f.value
355 self.ui.debug('received listkey for "%s": %i bytes\n'
355 self.ui.debug('received listkey for "%s": %i bytes\n'
356 % (namespace, len(d)))
356 % (namespace, len(d)))
357 yield pushkeymod.decodekeys(d)
357 yield pushkeymod.decodekeys(d)
358
358
359 def stream_out(self):
359 def stream_out(self):
360 return self._callstream('stream_out')
360 return self._callstream('stream_out')
361
361
362 def changegroup(self, nodes, kind):
362 def changegroup(self, nodes, kind):
363 n = encodelist(nodes)
363 n = encodelist(nodes)
364 f = self._callcompressable("changegroup", roots=n)
364 f = self._callcompressable("changegroup", roots=n)
365 return changegroupmod.cg1unpacker(f, 'UN')
365 return changegroupmod.cg1unpacker(f, 'UN')
366
366
367 def changegroupsubset(self, bases, heads, kind):
367 def changegroupsubset(self, bases, heads, kind):
368 self.requirecap('changegroupsubset', _('look up remote changes'))
368 self.requirecap('changegroupsubset', _('look up remote changes'))
369 bases = encodelist(bases)
369 bases = encodelist(bases)
370 heads = encodelist(heads)
370 heads = encodelist(heads)
371 f = self._callcompressable("changegroupsubset",
371 f = self._callcompressable("changegroupsubset",
372 bases=bases, heads=heads)
372 bases=bases, heads=heads)
373 return changegroupmod.cg1unpacker(f, 'UN')
373 return changegroupmod.cg1unpacker(f, 'UN')
374
374
375 def getbundle(self, source, **kwargs):
375 def getbundle(self, source, **kwargs):
376 self.requirecap('getbundle', _('look up remote changes'))
376 self.requirecap('getbundle', _('look up remote changes'))
377 opts = {}
377 opts = {}
378 bundlecaps = kwargs.get('bundlecaps')
378 bundlecaps = kwargs.get('bundlecaps')
379 if bundlecaps is not None:
379 if bundlecaps is not None:
380 kwargs['bundlecaps'] = sorted(bundlecaps)
380 kwargs['bundlecaps'] = sorted(bundlecaps)
381 else:
381 else:
382 bundlecaps = () # kwargs could have it to None
382 bundlecaps = () # kwargs could have it to None
383 for key, value in kwargs.iteritems():
383 for key, value in kwargs.iteritems():
384 if value is None:
384 if value is None:
385 continue
385 continue
386 keytype = gboptsmap.get(key)
386 keytype = gboptsmap.get(key)
387 if keytype is None:
387 if keytype is None:
388 assert False, 'unexpected'
388 assert False, 'unexpected'
389 elif keytype == 'nodes':
389 elif keytype == 'nodes':
390 value = encodelist(value)
390 value = encodelist(value)
391 elif keytype in ('csv', 'scsv'):
391 elif keytype in ('csv', 'scsv'):
392 value = ','.join(value)
392 value = ','.join(value)
393 elif keytype == 'boolean':
393 elif keytype == 'boolean':
394 value = '%i' % bool(value)
394 value = '%i' % bool(value)
395 elif keytype != 'plain':
395 elif keytype != 'plain':
396 raise KeyError('unknown getbundle option type %s'
396 raise KeyError('unknown getbundle option type %s'
397 % keytype)
397 % keytype)
398 opts[key] = value
398 opts[key] = value
399 f = self._callcompressable("getbundle", **opts)
399 f = self._callcompressable("getbundle", **opts)
400 if any((cap.startswith('HG2') for cap in bundlecaps)):
400 if any((cap.startswith('HG2') for cap in bundlecaps)):
401 return bundle2.getunbundler(self.ui, f)
401 return bundle2.getunbundler(self.ui, f)
402 else:
402 else:
403 return changegroupmod.cg1unpacker(f, 'UN')
403 return changegroupmod.cg1unpacker(f, 'UN')
404
404
405 def unbundle(self, cg, heads, url):
405 def unbundle(self, cg, heads, url):
406 '''Send cg (a readable file-like object representing the
406 '''Send cg (a readable file-like object representing the
407 changegroup to push, typically a chunkbuffer object) to the
407 changegroup to push, typically a chunkbuffer object) to the
408 remote server as a bundle.
408 remote server as a bundle.
409
409
410 When pushing a bundle10 stream, return an integer indicating the
410 When pushing a bundle10 stream, return an integer indicating the
411 result of the push (see localrepository.addchangegroup()).
411 result of the push (see localrepository.addchangegroup()).
412
412
413 When pushing a bundle20 stream, return a bundle20 stream.
413 When pushing a bundle20 stream, return a bundle20 stream.
414
414
415 `url` is the url the client thinks it's pushing to, which is
415 `url` is the url the client thinks it's pushing to, which is
416 visible to hooks.
416 visible to hooks.
417 '''
417 '''
418
418
419 if heads != ['force'] and self.capable('unbundlehash'):
419 if heads != ['force'] and self.capable('unbundlehash'):
420 heads = encodelist(['hashed',
420 heads = encodelist(['hashed',
421 hashlib.sha1(''.join(sorted(heads))).digest()])
421 hashlib.sha1(''.join(sorted(heads))).digest()])
422 else:
422 else:
423 heads = encodelist(heads)
423 heads = encodelist(heads)
424
424
425 if util.safehasattr(cg, 'deltaheader'):
425 if util.safehasattr(cg, 'deltaheader'):
426 # this a bundle10, do the old style call sequence
426 # this a bundle10, do the old style call sequence
427 ret, output = self._callpush("unbundle", cg, heads=heads)
427 ret, output = self._callpush("unbundle", cg, heads=heads)
428 if ret == "":
428 if ret == "":
429 raise error.ResponseError(
429 raise error.ResponseError(
430 _('push failed:'), output)
430 _('push failed:'), output)
431 try:
431 try:
432 ret = int(ret)
432 ret = int(ret)
433 except ValueError:
433 except ValueError:
434 raise error.ResponseError(
434 raise error.ResponseError(
435 _('push failed (unexpected response):'), ret)
435 _('push failed (unexpected response):'), ret)
436
436
437 for l in output.splitlines(True):
437 for l in output.splitlines(True):
438 self.ui.status(_('remote: '), l)
438 self.ui.status(_('remote: '), l)
439 else:
439 else:
440 # bundle2 push. Send a stream, fetch a stream.
440 # bundle2 push. Send a stream, fetch a stream.
441 stream = self._calltwowaystream('unbundle', cg, heads=heads)
441 stream = self._calltwowaystream('unbundle', cg, heads=heads)
442 ret = bundle2.getunbundler(self.ui, stream)
442 ret = bundle2.getunbundler(self.ui, stream)
443 return ret
443 return ret
444
444
445 def debugwireargs(self, one, two, three=None, four=None, five=None):
445 def debugwireargs(self, one, two, three=None, four=None, five=None):
446 # don't pass optional arguments left at their default value
446 # don't pass optional arguments left at their default value
447 opts = {}
447 opts = {}
448 if three is not None:
448 if three is not None:
449 opts['three'] = three
449 opts['three'] = three
450 if four is not None:
450 if four is not None:
451 opts['four'] = four
451 opts['four'] = four
452 return self._call('debugwireargs', one=one, two=two, **opts)
452 return self._call('debugwireargs', one=one, two=two, **opts)
453
453
454 def _call(self, cmd, **args):
454 def _call(self, cmd, **args):
455 """execute <cmd> on the server
455 """execute <cmd> on the server
456
456
457 The command is expected to return a simple string.
457 The command is expected to return a simple string.
458
458
459 returns the server reply as a string."""
459 returns the server reply as a string."""
460 raise NotImplementedError()
460 raise NotImplementedError()
461
461
462 def _callstream(self, cmd, **args):
462 def _callstream(self, cmd, **args):
463 """execute <cmd> on the server
463 """execute <cmd> on the server
464
464
465 The command is expected to return a stream. Note that if the
465 The command is expected to return a stream. Note that if the
466 command doesn't return a stream, _callstream behaves
466 command doesn't return a stream, _callstream behaves
467 differently for ssh and http peers.
467 differently for ssh and http peers.
468
468
469 returns the server reply as a file like object.
469 returns the server reply as a file like object.
470 """
470 """
471 raise NotImplementedError()
471 raise NotImplementedError()
472
472
473 def _callcompressable(self, cmd, **args):
473 def _callcompressable(self, cmd, **args):
474 """execute <cmd> on the server
474 """execute <cmd> on the server
475
475
476 The command is expected to return a stream.
476 The command is expected to return a stream.
477
477
478 The stream may have been compressed in some implementations. This
478 The stream may have been compressed in some implementations. This
479 function takes care of the decompression. This is the only difference
479 function takes care of the decompression. This is the only difference
480 with _callstream.
480 with _callstream.
481
481
482 returns the server reply as a file like object.
482 returns the server reply as a file like object.
483 """
483 """
484 raise NotImplementedError()
484 raise NotImplementedError()
485
485
486 def _callpush(self, cmd, fp, **args):
486 def _callpush(self, cmd, fp, **args):
487 """execute a <cmd> on server
487 """execute a <cmd> on server
488
488
489 The command is expected to be related to a push. Push has a special
489 The command is expected to be related to a push. Push has a special
490 return method.
490 return method.
491
491
492 returns the server reply as a (ret, output) tuple. ret is either
492 returns the server reply as a (ret, output) tuple. ret is either
493 empty (error) or a stringified int.
493 empty (error) or a stringified int.
494 """
494 """
495 raise NotImplementedError()
495 raise NotImplementedError()
496
496
497 def _calltwowaystream(self, cmd, fp, **args):
497 def _calltwowaystream(self, cmd, fp, **args):
498 """execute <cmd> on server
498 """execute <cmd> on server
499
499
500 The command will send a stream to the server and get a stream in reply.
500 The command will send a stream to the server and get a stream in reply.
501 """
501 """
502 raise NotImplementedError()
502 raise NotImplementedError()
503
503
504 def _abort(self, exception):
504 def _abort(self, exception):
505 """clearly abort the wire protocol connection and raise the exception
505 """clearly abort the wire protocol connection and raise the exception
506 """
506 """
507 raise NotImplementedError()
507 raise NotImplementedError()
508
508
509 # server side
509 # server side
510
510
511 # wire protocol command can either return a string or one of these classes.
511 # wire protocol command can either return a string or one of these classes.
512 class streamres(object):
512 class streamres(object):
513 """wireproto reply: binary stream
513 """wireproto reply: binary stream
514
514
515 The call was successful and the result is a stream.
515 The call was successful and the result is a stream.
516
516
517 Accepts either a generator or an object with a ``read(size)`` method.
517 Accepts either a generator or an object with a ``read(size)`` method.
518
518
519 ``v1compressible`` indicates whether this data can be compressed to
519 ``v1compressible`` indicates whether this data can be compressed to
520 "version 1" clients (technically: HTTP peers using
520 "version 1" clients (technically: HTTP peers using
521 application/mercurial-0.1 media type). This flag should NOT be used on
521 application/mercurial-0.1 media type). This flag should NOT be used on
522 new commands because new clients should support a more modern compression
522 new commands because new clients should support a more modern compression
523 mechanism.
523 mechanism.
524 """
524 """
525 def __init__(self, gen=None, reader=None, v1compressible=False):
525 def __init__(self, gen=None, reader=None, v1compressible=False):
526 self.gen = gen
526 self.gen = gen
527 self.reader = reader
527 self.reader = reader
528 self.v1compressible = v1compressible
528 self.v1compressible = v1compressible
529
529
530 class pushres(object):
530 class pushres(object):
531 """wireproto reply: success with simple integer return
531 """wireproto reply: success with simple integer return
532
532
533 The call was successful and returned an integer contained in `self.res`.
533 The call was successful and returned an integer contained in `self.res`.
534 """
534 """
535 def __init__(self, res):
535 def __init__(self, res):
536 self.res = res
536 self.res = res
537
537
538 class pusherr(object):
538 class pusherr(object):
539 """wireproto reply: failure
539 """wireproto reply: failure
540
540
541 The call failed. The `self.res` attribute contains the error message.
541 The call failed. The `self.res` attribute contains the error message.
542 """
542 """
543 def __init__(self, res):
543 def __init__(self, res):
544 self.res = res
544 self.res = res
545
545
546 class ooberror(object):
546 class ooberror(object):
547 """wireproto reply: failure of a batch of operation
547 """wireproto reply: failure of a batch of operation
548
548
549 Something failed during a batch call. The error message is stored in
549 Something failed during a batch call. The error message is stored in
550 `self.message`.
550 `self.message`.
551 """
551 """
552 def __init__(self, message):
552 def __init__(self, message):
553 self.message = message
553 self.message = message
554
554
555 def getdispatchrepo(repo, proto, command):
555 def getdispatchrepo(repo, proto, command):
556 """Obtain the repo used for processing wire protocol commands.
556 """Obtain the repo used for processing wire protocol commands.
557
557
558 The intent of this function is to serve as a monkeypatch point for
558 The intent of this function is to serve as a monkeypatch point for
559 extensions that need commands to operate on different repo views under
559 extensions that need commands to operate on different repo views under
560 specialized circumstances.
560 specialized circumstances.
561 """
561 """
562 return repo.filtered('served')
562 return repo.filtered('served')
563
563
564 def dispatch(repo, proto, command):
564 def dispatch(repo, proto, command):
565 repo = getdispatchrepo(repo, proto, command)
565 repo = getdispatchrepo(repo, proto, command)
566 func, spec = commands[command]
566 func, spec = commands[command]
567 args = proto.getargs(spec)
567 args = proto.getargs(spec)
568 return func(repo, proto, *args)
568 return func(repo, proto, *args)
569
569
570 def options(cmd, keys, others):
570 def options(cmd, keys, others):
571 opts = {}
571 opts = {}
572 for k in keys:
572 for k in keys:
573 if k in others:
573 if k in others:
574 opts[k] = others[k]
574 opts[k] = others[k]
575 del others[k]
575 del others[k]
576 if others:
576 if others:
577 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
577 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
578 % (cmd, ",".join(others)))
578 % (cmd, ",".join(others)))
579 return opts
579 return opts
580
580
581 def bundle1allowed(repo, action):
581 def bundle1allowed(repo, action):
582 """Whether a bundle1 operation is allowed from the server.
582 """Whether a bundle1 operation is allowed from the server.
583
583
584 Priority is:
584 Priority is:
585
585
586 1. server.bundle1gd.<action> (if generaldelta active)
586 1. server.bundle1gd.<action> (if generaldelta active)
587 2. server.bundle1.<action>
587 2. server.bundle1.<action>
588 3. server.bundle1gd (if generaldelta active)
588 3. server.bundle1gd (if generaldelta active)
589 4. server.bundle1
589 4. server.bundle1
590 """
590 """
591 ui = repo.ui
591 ui = repo.ui
592 gd = 'generaldelta' in repo.requirements
592 gd = 'generaldelta' in repo.requirements
593
593
594 if gd:
594 if gd:
595 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
595 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
596 if v is not None:
596 if v is not None:
597 return v
597 return v
598
598
599 v = ui.configbool('server', 'bundle1.%s' % action, None)
599 v = ui.configbool('server', 'bundle1.%s' % action, None)
600 if v is not None:
600 if v is not None:
601 return v
601 return v
602
602
603 if gd:
603 if gd:
604 v = ui.configbool('server', 'bundle1gd', None)
604 v = ui.configbool('server', 'bundle1gd', None)
605 if v is not None:
605 if v is not None:
606 return v
606 return v
607
607
608 return ui.configbool('server', 'bundle1', True)
608 return ui.configbool('server', 'bundle1', True)
609
609
610 # list of commands
610 # list of commands
611 commands = {}
611 commands = {}
612
612
613 def wireprotocommand(name, args=''):
613 def wireprotocommand(name, args=''):
614 """decorator for wire protocol command"""
614 """decorator for wire protocol command"""
615 def register(func):
615 def register(func):
616 commands[name] = (func, args)
616 commands[name] = (func, args)
617 return func
617 return func
618 return register
618 return register
619
619
620 @wireprotocommand('batch', 'cmds *')
620 @wireprotocommand('batch', 'cmds *')
621 def batch(repo, proto, cmds, others):
621 def batch(repo, proto, cmds, others):
622 repo = repo.filtered("served")
622 repo = repo.filtered("served")
623 res = []
623 res = []
624 for pair in cmds.split(';'):
624 for pair in cmds.split(';'):
625 op, args = pair.split(' ', 1)
625 op, args = pair.split(' ', 1)
626 vals = {}
626 vals = {}
627 for a in args.split(','):
627 for a in args.split(','):
628 if a:
628 if a:
629 n, v = a.split('=')
629 n, v = a.split('=')
630 vals[unescapearg(n)] = unescapearg(v)
630 vals[unescapearg(n)] = unescapearg(v)
631 func, spec = commands[op]
631 func, spec = commands[op]
632 if spec:
632 if spec:
633 keys = spec.split()
633 keys = spec.split()
634 data = {}
634 data = {}
635 for k in keys:
635 for k in keys:
636 if k == '*':
636 if k == '*':
637 star = {}
637 star = {}
638 for key in vals.keys():
638 for key in vals.keys():
639 if key not in keys:
639 if key not in keys:
640 star[key] = vals[key]
640 star[key] = vals[key]
641 data['*'] = star
641 data['*'] = star
642 else:
642 else:
643 data[k] = vals[k]
643 data[k] = vals[k]
644 result = func(repo, proto, *[data[k] for k in keys])
644 result = func(repo, proto, *[data[k] for k in keys])
645 else:
645 else:
646 result = func(repo, proto)
646 result = func(repo, proto)
647 if isinstance(result, ooberror):
647 if isinstance(result, ooberror):
648 return result
648 return result
649 res.append(escapearg(result))
649 res.append(escapearg(result))
650 return ';'.join(res)
650 return ';'.join(res)
651
651
652 @wireprotocommand('between', 'pairs')
652 @wireprotocommand('between', 'pairs')
653 def between(repo, proto, pairs):
653 def between(repo, proto, pairs):
654 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
654 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
655 r = []
655 r = []
656 for b in repo.between(pairs):
656 for b in repo.between(pairs):
657 r.append(encodelist(b) + "\n")
657 r.append(encodelist(b) + "\n")
658 return "".join(r)
658 return "".join(r)
659
659
660 @wireprotocommand('branchmap')
660 @wireprotocommand('branchmap')
661 def branchmap(repo, proto):
661 def branchmap(repo, proto):
662 branchmap = repo.branchmap()
662 branchmap = repo.branchmap()
663 heads = []
663 heads = []
664 for branch, nodes in branchmap.iteritems():
664 for branch, nodes in branchmap.iteritems():
665 branchname = urlreq.quote(encoding.fromlocal(branch))
665 branchname = urlreq.quote(encoding.fromlocal(branch))
666 branchnodes = encodelist(nodes)
666 branchnodes = encodelist(nodes)
667 heads.append('%s %s' % (branchname, branchnodes))
667 heads.append('%s %s' % (branchname, branchnodes))
668 return '\n'.join(heads)
668 return '\n'.join(heads)
669
669
670 @wireprotocommand('branches', 'nodes')
670 @wireprotocommand('branches', 'nodes')
671 def branches(repo, proto, nodes):
671 def branches(repo, proto, nodes):
672 nodes = decodelist(nodes)
672 nodes = decodelist(nodes)
673 r = []
673 r = []
674 for b in repo.branches(nodes):
674 for b in repo.branches(nodes):
675 r.append(encodelist(b) + "\n")
675 r.append(encodelist(b) + "\n")
676 return "".join(r)
676 return "".join(r)
677
677
678 @wireprotocommand('clonebundles', '')
678 @wireprotocommand('clonebundles', '')
679 def clonebundles(repo, proto):
679 def clonebundles(repo, proto):
680 """Server command for returning info for available bundles to seed clones.
680 """Server command for returning info for available bundles to seed clones.
681
681
682 Clients will parse this response and determine what bundle to fetch.
682 Clients will parse this response and determine what bundle to fetch.
683
683
684 Extensions may wrap this command to filter or dynamically emit data
684 Extensions may wrap this command to filter or dynamically emit data
685 depending on the request. e.g. you could advertise URLs for the closest
685 depending on the request. e.g. you could advertise URLs for the closest
686 data center given the client's IP address.
686 data center given the client's IP address.
687 """
687 """
688 return repo.opener.tryread('clonebundles.manifest')
688 return repo.opener.tryread('clonebundles.manifest')
689
689
690 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
690 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
691 'known', 'getbundle', 'unbundlehash', 'batch']
691 'known', 'getbundle', 'unbundlehash', 'batch']
692
692
693 def _capabilities(repo, proto):
693 def _capabilities(repo, proto):
694 """return a list of capabilities for a repo
694 """return a list of capabilities for a repo
695
695
696 This function exists to allow extensions to easily wrap capabilities
696 This function exists to allow extensions to easily wrap capabilities
697 computation
697 computation
698
698
699 - returns a lists: easy to alter
699 - returns a lists: easy to alter
700 - change done here will be propagated to both `capabilities` and `hello`
700 - change done here will be propagated to both `capabilities` and `hello`
701 command without any other action needed.
701 command without any other action needed.
702 """
702 """
703 # copy to prevent modification of the global list
703 # copy to prevent modification of the global list
704 caps = list(wireprotocaps)
704 caps = list(wireprotocaps)
705 if streamclone.allowservergeneration(repo.ui):
705 if streamclone.allowservergeneration(repo.ui):
706 if repo.ui.configbool('server', 'preferuncompressed', False):
706 if repo.ui.configbool('server', 'preferuncompressed', False):
707 caps.append('stream-preferred')
707 caps.append('stream-preferred')
708 requiredformats = repo.requirements & repo.supportedformats
708 requiredformats = repo.requirements & repo.supportedformats
709 # if our local revlogs are just revlogv1, add 'stream' cap
709 # if our local revlogs are just revlogv1, add 'stream' cap
710 if not requiredformats - set(('revlogv1',)):
710 if not requiredformats - set(('revlogv1',)):
711 caps.append('stream')
711 caps.append('stream')
712 # otherwise, add 'streamreqs' detailing our local revlog format
712 # otherwise, add 'streamreqs' detailing our local revlog format
713 else:
713 else:
714 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
714 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
715 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
715 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
716 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
716 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
717 caps.append('bundle2=' + urlreq.quote(capsblob))
717 caps.append('bundle2=' + urlreq.quote(capsblob))
718 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
718 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
719 caps.append(
719
720 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
720 if proto.name == 'http':
721 if repo.ui.configbool('experimental', 'httppostargs', False):
721 caps.append('httpheader=%d' %
722 caps.append('httppostargs')
722 repo.ui.configint('server', 'maxhttpheaderlen', 1024))
723 if repo.ui.configbool('experimental', 'httppostargs', False):
724 caps.append('httppostargs')
725
723 return caps
726 return caps
724
727
725 # If you are writing an extension and consider wrapping this function. Wrap
728 # If you are writing an extension and consider wrapping this function. Wrap
726 # `_capabilities` instead.
729 # `_capabilities` instead.
727 @wireprotocommand('capabilities')
730 @wireprotocommand('capabilities')
728 def capabilities(repo, proto):
731 def capabilities(repo, proto):
729 return ' '.join(_capabilities(repo, proto))
732 return ' '.join(_capabilities(repo, proto))
730
733
731 @wireprotocommand('changegroup', 'roots')
734 @wireprotocommand('changegroup', 'roots')
732 def changegroup(repo, proto, roots):
735 def changegroup(repo, proto, roots):
733 nodes = decodelist(roots)
736 nodes = decodelist(roots)
734 cg = changegroupmod.changegroup(repo, nodes, 'serve')
737 cg = changegroupmod.changegroup(repo, nodes, 'serve')
735 return streamres(reader=cg, v1compressible=True)
738 return streamres(reader=cg, v1compressible=True)
736
739
737 @wireprotocommand('changegroupsubset', 'bases heads')
740 @wireprotocommand('changegroupsubset', 'bases heads')
738 def changegroupsubset(repo, proto, bases, heads):
741 def changegroupsubset(repo, proto, bases, heads):
739 bases = decodelist(bases)
742 bases = decodelist(bases)
740 heads = decodelist(heads)
743 heads = decodelist(heads)
741 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
744 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
742 return streamres(reader=cg, v1compressible=True)
745 return streamres(reader=cg, v1compressible=True)
743
746
744 @wireprotocommand('debugwireargs', 'one two *')
747 @wireprotocommand('debugwireargs', 'one two *')
745 def debugwireargs(repo, proto, one, two, others):
748 def debugwireargs(repo, proto, one, two, others):
746 # only accept optional args from the known set
749 # only accept optional args from the known set
747 opts = options('debugwireargs', ['three', 'four'], others)
750 opts = options('debugwireargs', ['three', 'four'], others)
748 return repo.debugwireargs(one, two, **opts)
751 return repo.debugwireargs(one, two, **opts)
749
752
750 @wireprotocommand('getbundle', '*')
753 @wireprotocommand('getbundle', '*')
751 def getbundle(repo, proto, others):
754 def getbundle(repo, proto, others):
752 opts = options('getbundle', gboptsmap.keys(), others)
755 opts = options('getbundle', gboptsmap.keys(), others)
753 for k, v in opts.iteritems():
756 for k, v in opts.iteritems():
754 keytype = gboptsmap[k]
757 keytype = gboptsmap[k]
755 if keytype == 'nodes':
758 if keytype == 'nodes':
756 opts[k] = decodelist(v)
759 opts[k] = decodelist(v)
757 elif keytype == 'csv':
760 elif keytype == 'csv':
758 opts[k] = list(v.split(','))
761 opts[k] = list(v.split(','))
759 elif keytype == 'scsv':
762 elif keytype == 'scsv':
760 opts[k] = set(v.split(','))
763 opts[k] = set(v.split(','))
761 elif keytype == 'boolean':
764 elif keytype == 'boolean':
762 # Client should serialize False as '0', which is a non-empty string
765 # Client should serialize False as '0', which is a non-empty string
763 # so it evaluates as a True bool.
766 # so it evaluates as a True bool.
764 if v == '0':
767 if v == '0':
765 opts[k] = False
768 opts[k] = False
766 else:
769 else:
767 opts[k] = bool(v)
770 opts[k] = bool(v)
768 elif keytype != 'plain':
771 elif keytype != 'plain':
769 raise KeyError('unknown getbundle option type %s'
772 raise KeyError('unknown getbundle option type %s'
770 % keytype)
773 % keytype)
771
774
772 if not bundle1allowed(repo, 'pull'):
775 if not bundle1allowed(repo, 'pull'):
773 if not exchange.bundle2requested(opts.get('bundlecaps')):
776 if not exchange.bundle2requested(opts.get('bundlecaps')):
774 return ooberror(bundle2required)
777 return ooberror(bundle2required)
775
778
776 chunks = exchange.getbundlechunks(repo, 'serve', **opts)
779 chunks = exchange.getbundlechunks(repo, 'serve', **opts)
777 return streamres(gen=chunks, v1compressible=True)
780 return streamres(gen=chunks, v1compressible=True)
778
781
779 @wireprotocommand('heads')
782 @wireprotocommand('heads')
780 def heads(repo, proto):
783 def heads(repo, proto):
781 h = repo.heads()
784 h = repo.heads()
782 return encodelist(h) + "\n"
785 return encodelist(h) + "\n"
783
786
784 @wireprotocommand('hello')
787 @wireprotocommand('hello')
785 def hello(repo, proto):
788 def hello(repo, proto):
786 '''the hello command returns a set of lines describing various
789 '''the hello command returns a set of lines describing various
787 interesting things about the server, in an RFC822-like format.
790 interesting things about the server, in an RFC822-like format.
788 Currently the only one defined is "capabilities", which
791 Currently the only one defined is "capabilities", which
789 consists of a line in the form:
792 consists of a line in the form:
790
793
791 capabilities: space separated list of tokens
794 capabilities: space separated list of tokens
792 '''
795 '''
793 return "capabilities: %s\n" % (capabilities(repo, proto))
796 return "capabilities: %s\n" % (capabilities(repo, proto))
794
797
795 @wireprotocommand('listkeys', 'namespace')
798 @wireprotocommand('listkeys', 'namespace')
796 def listkeys(repo, proto, namespace):
799 def listkeys(repo, proto, namespace):
797 d = repo.listkeys(encoding.tolocal(namespace)).items()
800 d = repo.listkeys(encoding.tolocal(namespace)).items()
798 return pushkeymod.encodekeys(d)
801 return pushkeymod.encodekeys(d)
799
802
800 @wireprotocommand('lookup', 'key')
803 @wireprotocommand('lookup', 'key')
801 def lookup(repo, proto, key):
804 def lookup(repo, proto, key):
802 try:
805 try:
803 k = encoding.tolocal(key)
806 k = encoding.tolocal(key)
804 c = repo[k]
807 c = repo[k]
805 r = c.hex()
808 r = c.hex()
806 success = 1
809 success = 1
807 except Exception as inst:
810 except Exception as inst:
808 r = str(inst)
811 r = str(inst)
809 success = 0
812 success = 0
810 return "%s %s\n" % (success, r)
813 return "%s %s\n" % (success, r)
811
814
812 @wireprotocommand('known', 'nodes *')
815 @wireprotocommand('known', 'nodes *')
813 def known(repo, proto, nodes, others):
816 def known(repo, proto, nodes, others):
814 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
817 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
815
818
816 @wireprotocommand('pushkey', 'namespace key old new')
819 @wireprotocommand('pushkey', 'namespace key old new')
817 def pushkey(repo, proto, namespace, key, old, new):
820 def pushkey(repo, proto, namespace, key, old, new):
818 # compatibility with pre-1.8 clients which were accidentally
821 # compatibility with pre-1.8 clients which were accidentally
819 # sending raw binary nodes rather than utf-8-encoded hex
822 # sending raw binary nodes rather than utf-8-encoded hex
820 if len(new) == 20 and new.encode('string-escape') != new:
823 if len(new) == 20 and new.encode('string-escape') != new:
821 # looks like it could be a binary node
824 # looks like it could be a binary node
822 try:
825 try:
823 new.decode('utf-8')
826 new.decode('utf-8')
824 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
827 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
825 except UnicodeDecodeError:
828 except UnicodeDecodeError:
826 pass # binary, leave unmodified
829 pass # binary, leave unmodified
827 else:
830 else:
828 new = encoding.tolocal(new) # normal path
831 new = encoding.tolocal(new) # normal path
829
832
830 if util.safehasattr(proto, 'restore'):
833 if util.safehasattr(proto, 'restore'):
831
834
832 proto.redirect()
835 proto.redirect()
833
836
834 try:
837 try:
835 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
838 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
836 encoding.tolocal(old), new) or False
839 encoding.tolocal(old), new) or False
837 except error.Abort:
840 except error.Abort:
838 r = False
841 r = False
839
842
840 output = proto.restore()
843 output = proto.restore()
841
844
842 return '%s\n%s' % (int(r), output)
845 return '%s\n%s' % (int(r), output)
843
846
844 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
847 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
845 encoding.tolocal(old), new)
848 encoding.tolocal(old), new)
846 return '%s\n' % int(r)
849 return '%s\n' % int(r)
847
850
848 @wireprotocommand('stream_out')
851 @wireprotocommand('stream_out')
849 def stream(repo, proto):
852 def stream(repo, proto):
850 '''If the server supports streaming clone, it advertises the "stream"
853 '''If the server supports streaming clone, it advertises the "stream"
851 capability with a value representing the version and flags of the repo
854 capability with a value representing the version and flags of the repo
852 it is serving. Client checks to see if it understands the format.
855 it is serving. Client checks to see if it understands the format.
853 '''
856 '''
854 if not streamclone.allowservergeneration(repo.ui):
857 if not streamclone.allowservergeneration(repo.ui):
855 return '1\n'
858 return '1\n'
856
859
857 def getstream(it):
860 def getstream(it):
858 yield '0\n'
861 yield '0\n'
859 for chunk in it:
862 for chunk in it:
860 yield chunk
863 yield chunk
861
864
862 try:
865 try:
863 # LockError may be raised before the first result is yielded. Don't
866 # LockError may be raised before the first result is yielded. Don't
864 # emit output until we're sure we got the lock successfully.
867 # emit output until we're sure we got the lock successfully.
865 it = streamclone.generatev1wireproto(repo)
868 it = streamclone.generatev1wireproto(repo)
866 return streamres(gen=getstream(it))
869 return streamres(gen=getstream(it))
867 except error.LockError:
870 except error.LockError:
868 return '2\n'
871 return '2\n'
869
872
870 @wireprotocommand('unbundle', 'heads')
873 @wireprotocommand('unbundle', 'heads')
871 def unbundle(repo, proto, heads):
874 def unbundle(repo, proto, heads):
872 their_heads = decodelist(heads)
875 their_heads = decodelist(heads)
873
876
874 try:
877 try:
875 proto.redirect()
878 proto.redirect()
876
879
877 exchange.check_heads(repo, their_heads, 'preparing changes')
880 exchange.check_heads(repo, their_heads, 'preparing changes')
878
881
879 # write bundle data to temporary file because it can be big
882 # write bundle data to temporary file because it can be big
880 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
883 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
881 fp = os.fdopen(fd, 'wb+')
884 fp = os.fdopen(fd, 'wb+')
882 r = 0
885 r = 0
883 try:
886 try:
884 proto.getfile(fp)
887 proto.getfile(fp)
885 fp.seek(0)
888 fp.seek(0)
886 gen = exchange.readbundle(repo.ui, fp, None)
889 gen = exchange.readbundle(repo.ui, fp, None)
887 if (isinstance(gen, changegroupmod.cg1unpacker)
890 if (isinstance(gen, changegroupmod.cg1unpacker)
888 and not bundle1allowed(repo, 'push')):
891 and not bundle1allowed(repo, 'push')):
889 return ooberror(bundle2required)
892 return ooberror(bundle2required)
890
893
891 r = exchange.unbundle(repo, gen, their_heads, 'serve',
894 r = exchange.unbundle(repo, gen, their_heads, 'serve',
892 proto._client())
895 proto._client())
893 if util.safehasattr(r, 'addpart'):
896 if util.safehasattr(r, 'addpart'):
894 # The return looks streamable, we are in the bundle2 case and
897 # The return looks streamable, we are in the bundle2 case and
895 # should return a stream.
898 # should return a stream.
896 return streamres(gen=r.getchunks())
899 return streamres(gen=r.getchunks())
897 return pushres(r)
900 return pushres(r)
898
901
899 finally:
902 finally:
900 fp.close()
903 fp.close()
901 os.unlink(tempname)
904 os.unlink(tempname)
902
905
903 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
906 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
904 # handle non-bundle2 case first
907 # handle non-bundle2 case first
905 if not getattr(exc, 'duringunbundle2', False):
908 if not getattr(exc, 'duringunbundle2', False):
906 try:
909 try:
907 raise
910 raise
908 except error.Abort:
911 except error.Abort:
909 # The old code we moved used util.stderr directly.
912 # The old code we moved used util.stderr directly.
910 # We did not change it to minimise code change.
913 # We did not change it to minimise code change.
911 # This need to be moved to something proper.
914 # This need to be moved to something proper.
912 # Feel free to do it.
915 # Feel free to do it.
913 util.stderr.write("abort: %s\n" % exc)
916 util.stderr.write("abort: %s\n" % exc)
914 return pushres(0)
917 return pushres(0)
915 except error.PushRaced:
918 except error.PushRaced:
916 return pusherr(str(exc))
919 return pusherr(str(exc))
917
920
918 bundler = bundle2.bundle20(repo.ui)
921 bundler = bundle2.bundle20(repo.ui)
919 for out in getattr(exc, '_bundle2salvagedoutput', ()):
922 for out in getattr(exc, '_bundle2salvagedoutput', ()):
920 bundler.addpart(out)
923 bundler.addpart(out)
921 try:
924 try:
922 try:
925 try:
923 raise
926 raise
924 except error.PushkeyFailed as exc:
927 except error.PushkeyFailed as exc:
925 # check client caps
928 # check client caps
926 remotecaps = getattr(exc, '_replycaps', None)
929 remotecaps = getattr(exc, '_replycaps', None)
927 if (remotecaps is not None
930 if (remotecaps is not None
928 and 'pushkey' not in remotecaps.get('error', ())):
931 and 'pushkey' not in remotecaps.get('error', ())):
929 # no support remote side, fallback to Abort handler.
932 # no support remote side, fallback to Abort handler.
930 raise
933 raise
931 part = bundler.newpart('error:pushkey')
934 part = bundler.newpart('error:pushkey')
932 part.addparam('in-reply-to', exc.partid)
935 part.addparam('in-reply-to', exc.partid)
933 if exc.namespace is not None:
936 if exc.namespace is not None:
934 part.addparam('namespace', exc.namespace, mandatory=False)
937 part.addparam('namespace', exc.namespace, mandatory=False)
935 if exc.key is not None:
938 if exc.key is not None:
936 part.addparam('key', exc.key, mandatory=False)
939 part.addparam('key', exc.key, mandatory=False)
937 if exc.new is not None:
940 if exc.new is not None:
938 part.addparam('new', exc.new, mandatory=False)
941 part.addparam('new', exc.new, mandatory=False)
939 if exc.old is not None:
942 if exc.old is not None:
940 part.addparam('old', exc.old, mandatory=False)
943 part.addparam('old', exc.old, mandatory=False)
941 if exc.ret is not None:
944 if exc.ret is not None:
942 part.addparam('ret', exc.ret, mandatory=False)
945 part.addparam('ret', exc.ret, mandatory=False)
943 except error.BundleValueError as exc:
946 except error.BundleValueError as exc:
944 errpart = bundler.newpart('error:unsupportedcontent')
947 errpart = bundler.newpart('error:unsupportedcontent')
945 if exc.parttype is not None:
948 if exc.parttype is not None:
946 errpart.addparam('parttype', exc.parttype)
949 errpart.addparam('parttype', exc.parttype)
947 if exc.params:
950 if exc.params:
948 errpart.addparam('params', '\0'.join(exc.params))
951 errpart.addparam('params', '\0'.join(exc.params))
949 except error.Abort as exc:
952 except error.Abort as exc:
950 manargs = [('message', str(exc))]
953 manargs = [('message', str(exc))]
951 advargs = []
954 advargs = []
952 if exc.hint is not None:
955 if exc.hint is not None:
953 advargs.append(('hint', exc.hint))
956 advargs.append(('hint', exc.hint))
954 bundler.addpart(bundle2.bundlepart('error:abort',
957 bundler.addpart(bundle2.bundlepart('error:abort',
955 manargs, advargs))
958 manargs, advargs))
956 except error.PushRaced as exc:
959 except error.PushRaced as exc:
957 bundler.newpart('error:pushraced', [('message', str(exc))])
960 bundler.newpart('error:pushraced', [('message', str(exc))])
958 return streamres(gen=bundler.getchunks())
961 return streamres(gen=bundler.getchunks())
@@ -1,545 +1,545 b''
1 This test is a duplicate of 'test-http.t' feel free to factor out
1 This test is a duplicate of 'test-http.t' feel free to factor out
2 parts that are not bundle1/bundle2 specific.
2 parts that are not bundle1/bundle2 specific.
3
3
4 $ cat << EOF >> $HGRCPATH
4 $ cat << EOF >> $HGRCPATH
5 > [devel]
5 > [devel]
6 > # This test is dedicated to interaction through old bundle
6 > # This test is dedicated to interaction through old bundle
7 > legacy.exchange = bundle1
7 > legacy.exchange = bundle1
8 > [format] # temporary settings
8 > [format] # temporary settings
9 > usegeneraldelta=yes
9 > usegeneraldelta=yes
10 > EOF
10 > EOF
11
11
12
12
13 This test tries to exercise the ssh functionality with a dummy script
13 This test tries to exercise the ssh functionality with a dummy script
14
14
15 creating 'remote' repo
15 creating 'remote' repo
16
16
17 $ hg init remote
17 $ hg init remote
18 $ cd remote
18 $ cd remote
19 $ echo this > foo
19 $ echo this > foo
20 $ echo this > fooO
20 $ echo this > fooO
21 $ hg ci -A -m "init" foo fooO
21 $ hg ci -A -m "init" foo fooO
22
22
23 insert a closed branch (issue4428)
23 insert a closed branch (issue4428)
24
24
25 $ hg up null
25 $ hg up null
26 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
26 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
27 $ hg branch closed
27 $ hg branch closed
28 marked working directory as branch closed
28 marked working directory as branch closed
29 (branches are permanent and global, did you want a bookmark?)
29 (branches are permanent and global, did you want a bookmark?)
30 $ hg ci -mc0
30 $ hg ci -mc0
31 $ hg ci --close-branch -mc1
31 $ hg ci --close-branch -mc1
32 $ hg up -q default
32 $ hg up -q default
33
33
34 configure for serving
34 configure for serving
35
35
36 $ cat <<EOF > .hg/hgrc
36 $ cat <<EOF > .hg/hgrc
37 > [server]
37 > [server]
38 > uncompressed = True
38 > uncompressed = True
39 >
39 >
40 > [hooks]
40 > [hooks]
41 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
41 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
42 > EOF
42 > EOF
43 $ cd ..
43 $ cd ..
44
44
45 repo not found error
45 repo not found error
46
46
47 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
47 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
48 remote: abort: repository nonexistent not found!
48 remote: abort: repository nonexistent not found!
49 abort: no suitable response from remote hg!
49 abort: no suitable response from remote hg!
50 [255]
50 [255]
51
51
52 non-existent absolute path
52 non-existent absolute path
53
53
54 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
54 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
55 remote: abort: repository /$TESTTMP/nonexistent not found!
55 remote: abort: repository /$TESTTMP/nonexistent not found!
56 abort: no suitable response from remote hg!
56 abort: no suitable response from remote hg!
57 [255]
57 [255]
58
58
59 clone remote via stream
59 clone remote via stream
60
60
61 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
61 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
62 streaming all changes
62 streaming all changes
63 4 files to transfer, 602 bytes of data
63 4 files to transfer, 602 bytes of data
64 transferred 602 bytes in * seconds (*) (glob)
64 transferred 602 bytes in * seconds (*) (glob)
65 searching for changes
65 searching for changes
66 no changes found
66 no changes found
67 updating to branch default
67 updating to branch default
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ cd local-stream
69 $ cd local-stream
70 $ hg verify
70 $ hg verify
71 checking changesets
71 checking changesets
72 checking manifests
72 checking manifests
73 crosschecking files in changesets and manifests
73 crosschecking files in changesets and manifests
74 checking files
74 checking files
75 2 files, 3 changesets, 2 total revisions
75 2 files, 3 changesets, 2 total revisions
76 $ hg branches
76 $ hg branches
77 default 0:1160648e36ce
77 default 0:1160648e36ce
78 $ cd ..
78 $ cd ..
79
79
80 clone bookmarks via stream
80 clone bookmarks via stream
81
81
82 $ hg -R local-stream book mybook
82 $ hg -R local-stream book mybook
83 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
83 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
84 streaming all changes
84 streaming all changes
85 4 files to transfer, 602 bytes of data
85 4 files to transfer, 602 bytes of data
86 transferred 602 bytes in * seconds (*) (glob)
86 transferred 602 bytes in * seconds (*) (glob)
87 searching for changes
87 searching for changes
88 no changes found
88 no changes found
89 updating to branch default
89 updating to branch default
90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 $ cd stream2
91 $ cd stream2
92 $ hg book
92 $ hg book
93 mybook 0:1160648e36ce
93 mybook 0:1160648e36ce
94 $ cd ..
94 $ cd ..
95 $ rm -rf local-stream stream2
95 $ rm -rf local-stream stream2
96
96
97 clone remote via pull
97 clone remote via pull
98
98
99 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
99 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
100 requesting all changes
100 requesting all changes
101 adding changesets
101 adding changesets
102 adding manifests
102 adding manifests
103 adding file changes
103 adding file changes
104 added 3 changesets with 2 changes to 2 files
104 added 3 changesets with 2 changes to 2 files
105 updating to branch default
105 updating to branch default
106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
107
107
108 verify
108 verify
109
109
110 $ cd local
110 $ cd local
111 $ hg verify
111 $ hg verify
112 checking changesets
112 checking changesets
113 checking manifests
113 checking manifests
114 crosschecking files in changesets and manifests
114 crosschecking files in changesets and manifests
115 checking files
115 checking files
116 2 files, 3 changesets, 2 total revisions
116 2 files, 3 changesets, 2 total revisions
117 $ cat >> .hg/hgrc <<EOF
117 $ cat >> .hg/hgrc <<EOF
118 > [hooks]
118 > [hooks]
119 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
119 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
120 > EOF
120 > EOF
121
121
122 empty default pull
122 empty default pull
123
123
124 $ hg paths
124 $ hg paths
125 default = ssh://user@dummy/remote
125 default = ssh://user@dummy/remote
126 $ hg pull -e "python \"$TESTDIR/dummyssh\""
126 $ hg pull -e "python \"$TESTDIR/dummyssh\""
127 pulling from ssh://user@dummy/remote
127 pulling from ssh://user@dummy/remote
128 searching for changes
128 searching for changes
129 no changes found
129 no changes found
130
130
131 pull from wrong ssh URL
131 pull from wrong ssh URL
132
132
133 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
133 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
134 pulling from ssh://user@dummy/doesnotexist
134 pulling from ssh://user@dummy/doesnotexist
135 remote: abort: repository doesnotexist not found!
135 remote: abort: repository doesnotexist not found!
136 abort: no suitable response from remote hg!
136 abort: no suitable response from remote hg!
137 [255]
137 [255]
138
138
139 local change
139 local change
140
140
141 $ echo bleah > foo
141 $ echo bleah > foo
142 $ hg ci -m "add"
142 $ hg ci -m "add"
143
143
144 updating rc
144 updating rc
145
145
146 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
146 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
147 $ echo "[ui]" >> .hg/hgrc
147 $ echo "[ui]" >> .hg/hgrc
148 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
148 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
149
149
150 find outgoing
150 find outgoing
151
151
152 $ hg out ssh://user@dummy/remote
152 $ hg out ssh://user@dummy/remote
153 comparing with ssh://user@dummy/remote
153 comparing with ssh://user@dummy/remote
154 searching for changes
154 searching for changes
155 changeset: 3:a28a9d1a809c
155 changeset: 3:a28a9d1a809c
156 tag: tip
156 tag: tip
157 parent: 0:1160648e36ce
157 parent: 0:1160648e36ce
158 user: test
158 user: test
159 date: Thu Jan 01 00:00:00 1970 +0000
159 date: Thu Jan 01 00:00:00 1970 +0000
160 summary: add
160 summary: add
161
161
162
162
163 find incoming on the remote side
163 find incoming on the remote side
164
164
165 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
165 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
166 comparing with ssh://user@dummy/local
166 comparing with ssh://user@dummy/local
167 searching for changes
167 searching for changes
168 changeset: 3:a28a9d1a809c
168 changeset: 3:a28a9d1a809c
169 tag: tip
169 tag: tip
170 parent: 0:1160648e36ce
170 parent: 0:1160648e36ce
171 user: test
171 user: test
172 date: Thu Jan 01 00:00:00 1970 +0000
172 date: Thu Jan 01 00:00:00 1970 +0000
173 summary: add
173 summary: add
174
174
175
175
176 find incoming on the remote side (using absolute path)
176 find incoming on the remote side (using absolute path)
177
177
178 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
178 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
179 comparing with ssh://user@dummy/$TESTTMP/local
179 comparing with ssh://user@dummy/$TESTTMP/local
180 searching for changes
180 searching for changes
181 changeset: 3:a28a9d1a809c
181 changeset: 3:a28a9d1a809c
182 tag: tip
182 tag: tip
183 parent: 0:1160648e36ce
183 parent: 0:1160648e36ce
184 user: test
184 user: test
185 date: Thu Jan 01 00:00:00 1970 +0000
185 date: Thu Jan 01 00:00:00 1970 +0000
186 summary: add
186 summary: add
187
187
188
188
189 push
189 push
190
190
191 $ hg push
191 $ hg push
192 pushing to ssh://user@dummy/remote
192 pushing to ssh://user@dummy/remote
193 searching for changes
193 searching for changes
194 remote: adding changesets
194 remote: adding changesets
195 remote: adding manifests
195 remote: adding manifests
196 remote: adding file changes
196 remote: adding file changes
197 remote: added 1 changesets with 1 changes to 1 files
197 remote: added 1 changesets with 1 changes to 1 files
198 $ cd ../remote
198 $ cd ../remote
199
199
200 check remote tip
200 check remote tip
201
201
202 $ hg tip
202 $ hg tip
203 changeset: 3:a28a9d1a809c
203 changeset: 3:a28a9d1a809c
204 tag: tip
204 tag: tip
205 parent: 0:1160648e36ce
205 parent: 0:1160648e36ce
206 user: test
206 user: test
207 date: Thu Jan 01 00:00:00 1970 +0000
207 date: Thu Jan 01 00:00:00 1970 +0000
208 summary: add
208 summary: add
209
209
210 $ hg verify
210 $ hg verify
211 checking changesets
211 checking changesets
212 checking manifests
212 checking manifests
213 crosschecking files in changesets and manifests
213 crosschecking files in changesets and manifests
214 checking files
214 checking files
215 2 files, 4 changesets, 3 total revisions
215 2 files, 4 changesets, 3 total revisions
216 $ hg cat -r tip foo
216 $ hg cat -r tip foo
217 bleah
217 bleah
218 $ echo z > z
218 $ echo z > z
219 $ hg ci -A -m z z
219 $ hg ci -A -m z z
220 created new head
220 created new head
221
221
222 test pushkeys and bookmarks
222 test pushkeys and bookmarks
223
223
224 $ cd ../local
224 $ cd ../local
225 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
225 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
226 bookmarks
226 bookmarks
227 namespaces
227 namespaces
228 phases
228 phases
229 $ hg book foo -r 0
229 $ hg book foo -r 0
230 $ hg out -B
230 $ hg out -B
231 comparing with ssh://user@dummy/remote
231 comparing with ssh://user@dummy/remote
232 searching for changed bookmarks
232 searching for changed bookmarks
233 foo 1160648e36ce
233 foo 1160648e36ce
234 $ hg push -B foo
234 $ hg push -B foo
235 pushing to ssh://user@dummy/remote
235 pushing to ssh://user@dummy/remote
236 searching for changes
236 searching for changes
237 no changes found
237 no changes found
238 exporting bookmark foo
238 exporting bookmark foo
239 [1]
239 [1]
240 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
240 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
241 foo 1160648e36cec0054048a7edc4110c6f84fde594
241 foo 1160648e36cec0054048a7edc4110c6f84fde594
242 $ hg book -f foo
242 $ hg book -f foo
243 $ hg push --traceback
243 $ hg push --traceback
244 pushing to ssh://user@dummy/remote
244 pushing to ssh://user@dummy/remote
245 searching for changes
245 searching for changes
246 no changes found
246 no changes found
247 updating bookmark foo
247 updating bookmark foo
248 [1]
248 [1]
249 $ hg book -d foo
249 $ hg book -d foo
250 $ hg in -B
250 $ hg in -B
251 comparing with ssh://user@dummy/remote
251 comparing with ssh://user@dummy/remote
252 searching for changed bookmarks
252 searching for changed bookmarks
253 foo a28a9d1a809c
253 foo a28a9d1a809c
254 $ hg book -f -r 0 foo
254 $ hg book -f -r 0 foo
255 $ hg pull -B foo
255 $ hg pull -B foo
256 pulling from ssh://user@dummy/remote
256 pulling from ssh://user@dummy/remote
257 no changes found
257 no changes found
258 updating bookmark foo
258 updating bookmark foo
259 $ hg book -d foo
259 $ hg book -d foo
260 $ hg push -B foo
260 $ hg push -B foo
261 pushing to ssh://user@dummy/remote
261 pushing to ssh://user@dummy/remote
262 searching for changes
262 searching for changes
263 no changes found
263 no changes found
264 deleting remote bookmark foo
264 deleting remote bookmark foo
265 [1]
265 [1]
266
266
267 a bad, evil hook that prints to stdout
267 a bad, evil hook that prints to stdout
268
268
269 $ cat <<EOF > $TESTTMP/badhook
269 $ cat <<EOF > $TESTTMP/badhook
270 > import sys
270 > import sys
271 > sys.stdout.write("KABOOM\n")
271 > sys.stdout.write("KABOOM\n")
272 > EOF
272 > EOF
273
273
274 $ echo '[hooks]' >> ../remote/.hg/hgrc
274 $ echo '[hooks]' >> ../remote/.hg/hgrc
275 $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
275 $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
276 $ echo r > r
276 $ echo r > r
277 $ hg ci -A -m z r
277 $ hg ci -A -m z r
278
278
279 push should succeed even though it has an unexpected response
279 push should succeed even though it has an unexpected response
280
280
281 $ hg push
281 $ hg push
282 pushing to ssh://user@dummy/remote
282 pushing to ssh://user@dummy/remote
283 searching for changes
283 searching for changes
284 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
284 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
285 remote: adding changesets
285 remote: adding changesets
286 remote: adding manifests
286 remote: adding manifests
287 remote: adding file changes
287 remote: adding file changes
288 remote: added 1 changesets with 1 changes to 1 files
288 remote: added 1 changesets with 1 changes to 1 files
289 remote: KABOOM
289 remote: KABOOM
290 $ hg -R ../remote heads
290 $ hg -R ../remote heads
291 changeset: 5:1383141674ec
291 changeset: 5:1383141674ec
292 tag: tip
292 tag: tip
293 parent: 3:a28a9d1a809c
293 parent: 3:a28a9d1a809c
294 user: test
294 user: test
295 date: Thu Jan 01 00:00:00 1970 +0000
295 date: Thu Jan 01 00:00:00 1970 +0000
296 summary: z
296 summary: z
297
297
298 changeset: 4:6c0482d977a3
298 changeset: 4:6c0482d977a3
299 parent: 0:1160648e36ce
299 parent: 0:1160648e36ce
300 user: test
300 user: test
301 date: Thu Jan 01 00:00:00 1970 +0000
301 date: Thu Jan 01 00:00:00 1970 +0000
302 summary: z
302 summary: z
303
303
304
304
305 clone bookmarks
305 clone bookmarks
306
306
307 $ hg -R ../remote bookmark test
307 $ hg -R ../remote bookmark test
308 $ hg -R ../remote bookmarks
308 $ hg -R ../remote bookmarks
309 * test 4:6c0482d977a3
309 * test 4:6c0482d977a3
310 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
310 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
311 requesting all changes
311 requesting all changes
312 adding changesets
312 adding changesets
313 adding manifests
313 adding manifests
314 adding file changes
314 adding file changes
315 added 6 changesets with 5 changes to 4 files (+1 heads)
315 added 6 changesets with 5 changes to 4 files (+1 heads)
316 updating to branch default
316 updating to branch default
317 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
317 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
318 $ hg -R local-bookmarks bookmarks
318 $ hg -R local-bookmarks bookmarks
319 test 4:6c0482d977a3
319 test 4:6c0482d977a3
320
320
321 passwords in ssh urls are not supported
321 passwords in ssh urls are not supported
322 (we use a glob here because different Python versions give different
322 (we use a glob here because different Python versions give different
323 results here)
323 results here)
324
324
325 $ hg push ssh://user:erroneouspwd@dummy/remote
325 $ hg push ssh://user:erroneouspwd@dummy/remote
326 pushing to ssh://user:*@dummy/remote (glob)
326 pushing to ssh://user:*@dummy/remote (glob)
327 abort: password in URL not supported!
327 abort: password in URL not supported!
328 [255]
328 [255]
329
329
330 $ cd ..
330 $ cd ..
331
331
332 hide outer repo
332 hide outer repo
333 $ hg init
333 $ hg init
334
334
335 Test remote paths with spaces (issue2983):
335 Test remote paths with spaces (issue2983):
336
336
337 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
337 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
338 $ touch "$TESTTMP/a repo/test"
338 $ touch "$TESTTMP/a repo/test"
339 $ hg -R 'a repo' commit -A -m "test"
339 $ hg -R 'a repo' commit -A -m "test"
340 adding test
340 adding test
341 $ hg -R 'a repo' tag tag
341 $ hg -R 'a repo' tag tag
342 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
342 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
343 73649e48688a
343 73649e48688a
344
344
345 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
345 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
346 abort: unknown revision 'noNoNO'!
346 abort: unknown revision 'noNoNO'!
347 [255]
347 [255]
348
348
349 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
349 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
350
350
351 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
351 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
352 destination directory: a repo
352 destination directory: a repo
353 abort: destination 'a repo' is not empty
353 abort: destination 'a repo' is not empty
354 [255]
354 [255]
355
355
356 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
356 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
357 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
357 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
358 parameters:
358 parameters:
359
359
360 $ cat > ssh.sh << EOF
360 $ cat > ssh.sh << EOF
361 > userhost="\$1"
361 > userhost="\$1"
362 > SSH_ORIGINAL_COMMAND="\$2"
362 > SSH_ORIGINAL_COMMAND="\$2"
363 > export SSH_ORIGINAL_COMMAND
363 > export SSH_ORIGINAL_COMMAND
364 > PYTHONPATH="$PYTHONPATH"
364 > PYTHONPATH="$PYTHONPATH"
365 > export PYTHONPATH
365 > export PYTHONPATH
366 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
366 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
367 > EOF
367 > EOF
368
368
369 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
369 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
370 73649e48688a
370 73649e48688a
371
371
372 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
372 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
373 remote: Illegal repository "$TESTTMP/a'repo" (glob)
373 remote: Illegal repository "$TESTTMP/a'repo" (glob)
374 abort: no suitable response from remote hg!
374 abort: no suitable response from remote hg!
375 [255]
375 [255]
376
376
377 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
377 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
378 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
378 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
379 abort: no suitable response from remote hg!
379 abort: no suitable response from remote hg!
380 [255]
380 [255]
381
381
382 $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" python "$TESTDIR/../contrib/hg-ssh"
382 $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" python "$TESTDIR/../contrib/hg-ssh"
383 Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
383 Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
384 [255]
384 [255]
385
385
386 Test hg-ssh in read-only mode:
386 Test hg-ssh in read-only mode:
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" --read-only "$TESTTMP/remote"
394 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
395 > EOF
395 > EOF
396
396
397 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
397 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
398 requesting all changes
398 requesting all changes
399 adding changesets
399 adding changesets
400 adding manifests
400 adding manifests
401 adding file changes
401 adding file changes
402 added 6 changesets with 5 changes to 4 files (+1 heads)
402 added 6 changesets with 5 changes to 4 files (+1 heads)
403 updating to branch default
403 updating to branch default
404 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
404 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
405
405
406 $ cd read-only-local
406 $ cd read-only-local
407 $ echo "baz" > bar
407 $ echo "baz" > bar
408 $ hg ci -A -m "unpushable commit" bar
408 $ hg ci -A -m "unpushable commit" bar
409 $ hg push --ssh "sh ../ssh.sh"
409 $ hg push --ssh "sh ../ssh.sh"
410 pushing to ssh://user@dummy/*/remote (glob)
410 pushing to ssh://user@dummy/*/remote (glob)
411 searching for changes
411 searching for changes
412 remote: Permission denied
412 remote: Permission denied
413 remote: abort: pretxnopen.hg-ssh hook failed
413 remote: abort: pretxnopen.hg-ssh hook failed
414 remote: Permission denied
414 remote: Permission denied
415 remote: pushkey-abort: prepushkey.hg-ssh hook failed
415 remote: pushkey-abort: prepushkey.hg-ssh hook failed
416 updating 6c0482d977a3 to public failed!
416 updating 6c0482d977a3 to public failed!
417 [1]
417 [1]
418
418
419 $ cd ..
419 $ cd ..
420
420
421 stderr from remote commands should be printed before stdout from local code (issue4336)
421 stderr from remote commands should be printed before stdout from local code (issue4336)
422
422
423 $ hg clone remote stderr-ordering
423 $ hg clone remote stderr-ordering
424 updating to branch default
424 updating to branch default
425 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 $ cd stderr-ordering
426 $ cd stderr-ordering
427 $ cat >> localwrite.py << EOF
427 $ cat >> localwrite.py << EOF
428 > from mercurial import exchange, extensions
428 > from mercurial import exchange, extensions
429 >
429 >
430 > def wrappedpush(orig, repo, *args, **kwargs):
430 > def wrappedpush(orig, repo, *args, **kwargs):
431 > res = orig(repo, *args, **kwargs)
431 > res = orig(repo, *args, **kwargs)
432 > repo.ui.write('local stdout\n')
432 > repo.ui.write('local stdout\n')
433 > return res
433 > return res
434 >
434 >
435 > def extsetup(ui):
435 > def extsetup(ui):
436 > extensions.wrapfunction(exchange, 'push', wrappedpush)
436 > extensions.wrapfunction(exchange, 'push', wrappedpush)
437 > EOF
437 > EOF
438
438
439 $ cat >> .hg/hgrc << EOF
439 $ cat >> .hg/hgrc << EOF
440 > [paths]
440 > [paths]
441 > default-push = ssh://user@dummy/remote
441 > default-push = ssh://user@dummy/remote
442 > [ui]
442 > [ui]
443 > ssh = python "$TESTDIR/dummyssh"
443 > ssh = python "$TESTDIR/dummyssh"
444 > [extensions]
444 > [extensions]
445 > localwrite = localwrite.py
445 > localwrite = localwrite.py
446 > EOF
446 > EOF
447
447
448 $ echo localwrite > foo
448 $ echo localwrite > foo
449 $ hg commit -m 'testing localwrite'
449 $ hg commit -m 'testing localwrite'
450 $ hg push
450 $ hg push
451 pushing to ssh://user@dummy/remote
451 pushing to ssh://user@dummy/remote
452 searching for changes
452 searching for changes
453 remote: adding changesets
453 remote: adding changesets
454 remote: adding manifests
454 remote: adding manifests
455 remote: adding file changes
455 remote: adding file changes
456 remote: added 1 changesets with 1 changes to 1 files
456 remote: added 1 changesets with 1 changes to 1 files
457 remote: KABOOM
457 remote: KABOOM
458 local stdout
458 local stdout
459
459
460 debug output
460 debug output
461
461
462 $ hg pull --debug ssh://user@dummy/remote
462 $ hg pull --debug ssh://user@dummy/remote
463 pulling from ssh://user@dummy/remote
463 pulling from ssh://user@dummy/remote
464 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
464 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
465 sending hello command
465 sending hello command
466 sending between command
466 sending between command
467 remote: 371
467 remote: 355
468 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
468 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
469 remote: 1
469 remote: 1
470 preparing listkeys for "bookmarks"
470 preparing listkeys for "bookmarks"
471 sending listkeys command
471 sending listkeys command
472 received listkey for "bookmarks": 45 bytes
472 received listkey for "bookmarks": 45 bytes
473 query 1; heads
473 query 1; heads
474 sending batch command
474 sending batch command
475 searching for changes
475 searching for changes
476 all remote heads known locally
476 all remote heads known locally
477 no changes found
477 no changes found
478 preparing listkeys for "phases"
478 preparing listkeys for "phases"
479 sending listkeys command
479 sending listkeys command
480 received listkey for "phases": 15 bytes
480 received listkey for "phases": 15 bytes
481 checking for updated bookmarks
481 checking for updated bookmarks
482
482
483 $ cd ..
483 $ cd ..
484
484
485 $ cat dummylog
485 $ cat dummylog
486 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
486 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
487 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
487 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
488 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
488 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
489 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
489 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
490 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
490 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
491 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
491 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
492 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
492 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
493 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
493 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
494 Got arguments 1:user@dummy 2:hg -R local serve --stdio
494 Got arguments 1:user@dummy 2:hg -R local serve --stdio
495 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
495 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
496 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
496 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
497 changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
497 changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
498 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
498 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
499 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
499 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
501 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
501 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
507 changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 Got arguments 1:user@dummy 2:hg init 'a repo'
509 Got arguments 1:user@dummy 2:hg init 'a repo'
510 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
510 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
511 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
511 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
512 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
512 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
513 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
513 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
514 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
514 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
515 changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
515 changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
516 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
516 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
517
517
518 remote hook failure is attributed to remote
518 remote hook failure is attributed to remote
519
519
520 $ cat > $TESTTMP/failhook << EOF
520 $ cat > $TESTTMP/failhook << EOF
521 > def hook(ui, repo, **kwargs):
521 > def hook(ui, repo, **kwargs):
522 > ui.write('hook failure!\n')
522 > ui.write('hook failure!\n')
523 > ui.flush()
523 > ui.flush()
524 > return 1
524 > return 1
525 > EOF
525 > EOF
526
526
527 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
527 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
528
528
529 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
529 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
530 $ cd hookout
530 $ cd hookout
531 $ touch hookfailure
531 $ touch hookfailure
532 $ hg -q commit -A -m 'remote hook failure'
532 $ hg -q commit -A -m 'remote hook failure'
533 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
533 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
534 pushing to ssh://user@dummy/remote
534 pushing to ssh://user@dummy/remote
535 searching for changes
535 searching for changes
536 remote: adding changesets
536 remote: adding changesets
537 remote: adding manifests
537 remote: adding manifests
538 remote: adding file changes
538 remote: adding file changes
539 remote: added 1 changesets with 1 changes to 1 files
539 remote: added 1 changesets with 1 changes to 1 files
540 remote: hook failure!
540 remote: hook failure!
541 remote: transaction abort!
541 remote: transaction abort!
542 remote: rollback completed
542 remote: rollback completed
543 remote: abort: pretxnchangegroup.fail hook failed
543 remote: abort: pretxnchangegroup.fail hook failed
544 [1]
544 [1]
545
545
@@ -1,550 +1,550 b''
1
1
2 This test tries to exercise the ssh functionality with a dummy script
2 This test tries to exercise the ssh functionality with a dummy script
3
3
4 $ cat <<EOF >> $HGRCPATH
4 $ cat <<EOF >> $HGRCPATH
5 > [format]
5 > [format]
6 > usegeneraldelta=yes
6 > usegeneraldelta=yes
7 > EOF
7 > EOF
8
8
9 creating 'remote' repo
9 creating 'remote' repo
10
10
11 $ hg init remote
11 $ hg init remote
12 $ cd remote
12 $ cd remote
13 $ echo this > foo
13 $ echo this > foo
14 $ echo this > fooO
14 $ echo this > fooO
15 $ hg ci -A -m "init" foo fooO
15 $ hg ci -A -m "init" foo fooO
16
16
17 insert a closed branch (issue4428)
17 insert a closed branch (issue4428)
18
18
19 $ hg up null
19 $ hg up null
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 $ hg branch closed
21 $ hg branch closed
22 marked working directory as branch closed
22 marked working directory as branch closed
23 (branches are permanent and global, did you want a bookmark?)
23 (branches are permanent and global, did you want a bookmark?)
24 $ hg ci -mc0
24 $ hg ci -mc0
25 $ hg ci --close-branch -mc1
25 $ hg ci --close-branch -mc1
26 $ hg up -q default
26 $ hg up -q default
27
27
28 configure for serving
28 configure for serving
29
29
30 $ cat <<EOF > .hg/hgrc
30 $ cat <<EOF > .hg/hgrc
31 > [server]
31 > [server]
32 > uncompressed = True
32 > uncompressed = True
33 >
33 >
34 > [hooks]
34 > [hooks]
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
36 > EOF
36 > EOF
37 $ cd ..
37 $ cd ..
38
38
39 repo not found error
39 repo not found error
40
40
41 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
41 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
42 remote: abort: repository nonexistent not found!
42 remote: abort: repository nonexistent not found!
43 abort: no suitable response from remote hg!
43 abort: no suitable response from remote hg!
44 [255]
44 [255]
45
45
46 non-existent absolute path
46 non-existent absolute path
47
47
48 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
48 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
49 remote: abort: repository $TESTTMP/nonexistent not found!
49 remote: abort: repository $TESTTMP/nonexistent not found!
50 abort: no suitable response from remote hg!
50 abort: no suitable response from remote hg!
51 [255]
51 [255]
52
52
53 clone remote via stream
53 clone remote via stream
54
54
55 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
55 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
56 streaming all changes
56 streaming all changes
57 4 files to transfer, 602 bytes of data
57 4 files to transfer, 602 bytes of data
58 transferred 602 bytes in * seconds (*) (glob)
58 transferred 602 bytes in * seconds (*) (glob)
59 searching for changes
59 searching for changes
60 no changes found
60 no changes found
61 updating to branch default
61 updating to branch default
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ cd local-stream
63 $ cd local-stream
64 $ hg verify
64 $ hg verify
65 checking changesets
65 checking changesets
66 checking manifests
66 checking manifests
67 crosschecking files in changesets and manifests
67 crosschecking files in changesets and manifests
68 checking files
68 checking files
69 2 files, 3 changesets, 2 total revisions
69 2 files, 3 changesets, 2 total revisions
70 $ hg branches
70 $ hg branches
71 default 0:1160648e36ce
71 default 0:1160648e36ce
72 $ cd ..
72 $ cd ..
73
73
74 clone bookmarks via stream
74 clone bookmarks via stream
75
75
76 $ hg -R local-stream book mybook
76 $ hg -R local-stream book mybook
77 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
77 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
78 streaming all changes
78 streaming all changes
79 4 files to transfer, 602 bytes of data
79 4 files to transfer, 602 bytes of data
80 transferred 602 bytes in * seconds (*) (glob)
80 transferred 602 bytes in * seconds (*) (glob)
81 searching for changes
81 searching for changes
82 no changes found
82 no changes found
83 updating to branch default
83 updating to branch default
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 $ cd stream2
85 $ cd stream2
86 $ hg book
86 $ hg book
87 mybook 0:1160648e36ce
87 mybook 0:1160648e36ce
88 $ cd ..
88 $ cd ..
89 $ rm -rf local-stream stream2
89 $ rm -rf local-stream stream2
90
90
91 clone remote via pull
91 clone remote via pull
92
92
93 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
93 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
94 requesting all changes
94 requesting all changes
95 adding changesets
95 adding changesets
96 adding manifests
96 adding manifests
97 adding file changes
97 adding file changes
98 added 3 changesets with 2 changes to 2 files
98 added 3 changesets with 2 changes to 2 files
99 updating to branch default
99 updating to branch default
100 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
101
101
102 verify
102 verify
103
103
104 $ cd local
104 $ cd local
105 $ hg verify
105 $ hg verify
106 checking changesets
106 checking changesets
107 checking manifests
107 checking manifests
108 crosschecking files in changesets and manifests
108 crosschecking files in changesets and manifests
109 checking files
109 checking files
110 2 files, 3 changesets, 2 total revisions
110 2 files, 3 changesets, 2 total revisions
111 $ cat >> .hg/hgrc <<EOF
111 $ cat >> .hg/hgrc <<EOF
112 > [hooks]
112 > [hooks]
113 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
113 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
114 > EOF
114 > EOF
115
115
116 empty default pull
116 empty default pull
117
117
118 $ hg paths
118 $ hg paths
119 default = ssh://user@dummy/remote
119 default = ssh://user@dummy/remote
120 $ hg pull -e "python \"$TESTDIR/dummyssh\""
120 $ hg pull -e "python \"$TESTDIR/dummyssh\""
121 pulling from ssh://user@dummy/remote
121 pulling from ssh://user@dummy/remote
122 searching for changes
122 searching for changes
123 no changes found
123 no changes found
124
124
125 pull from wrong ssh URL
125 pull from wrong ssh URL
126
126
127 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
127 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
128 pulling from ssh://user@dummy/doesnotexist
128 pulling from ssh://user@dummy/doesnotexist
129 remote: abort: repository doesnotexist not found!
129 remote: abort: repository doesnotexist not found!
130 abort: no suitable response from remote hg!
130 abort: no suitable response from remote hg!
131 [255]
131 [255]
132
132
133 local change
133 local change
134
134
135 $ echo bleah > foo
135 $ echo bleah > foo
136 $ hg ci -m "add"
136 $ hg ci -m "add"
137
137
138 updating rc
138 updating rc
139
139
140 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
140 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
141 $ echo "[ui]" >> .hg/hgrc
141 $ echo "[ui]" >> .hg/hgrc
142 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
142 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
143
143
144 find outgoing
144 find outgoing
145
145
146 $ hg out ssh://user@dummy/remote
146 $ hg out ssh://user@dummy/remote
147 comparing with ssh://user@dummy/remote
147 comparing with ssh://user@dummy/remote
148 searching for changes
148 searching for changes
149 changeset: 3:a28a9d1a809c
149 changeset: 3:a28a9d1a809c
150 tag: tip
150 tag: tip
151 parent: 0:1160648e36ce
151 parent: 0:1160648e36ce
152 user: test
152 user: test
153 date: Thu Jan 01 00:00:00 1970 +0000
153 date: Thu Jan 01 00:00:00 1970 +0000
154 summary: add
154 summary: add
155
155
156
156
157 find incoming on the remote side
157 find incoming on the remote side
158
158
159 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
159 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
160 comparing with ssh://user@dummy/local
160 comparing with ssh://user@dummy/local
161 searching for changes
161 searching for changes
162 changeset: 3:a28a9d1a809c
162 changeset: 3:a28a9d1a809c
163 tag: tip
163 tag: tip
164 parent: 0:1160648e36ce
164 parent: 0:1160648e36ce
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:00 1970 +0000
166 date: Thu Jan 01 00:00:00 1970 +0000
167 summary: add
167 summary: add
168
168
169
169
170 find incoming on the remote side (using absolute path)
170 find incoming on the remote side (using absolute path)
171
171
172 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
172 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
173 comparing with ssh://user@dummy/$TESTTMP/local
173 comparing with ssh://user@dummy/$TESTTMP/local
174 searching for changes
174 searching for changes
175 changeset: 3:a28a9d1a809c
175 changeset: 3:a28a9d1a809c
176 tag: tip
176 tag: tip
177 parent: 0:1160648e36ce
177 parent: 0:1160648e36ce
178 user: test
178 user: test
179 date: Thu Jan 01 00:00:00 1970 +0000
179 date: Thu Jan 01 00:00:00 1970 +0000
180 summary: add
180 summary: add
181
181
182
182
183 push
183 push
184
184
185 $ hg push
185 $ hg push
186 pushing to ssh://user@dummy/remote
186 pushing to ssh://user@dummy/remote
187 searching for changes
187 searching for changes
188 remote: adding changesets
188 remote: adding changesets
189 remote: adding manifests
189 remote: adding manifests
190 remote: adding file changes
190 remote: adding file changes
191 remote: added 1 changesets with 1 changes to 1 files
191 remote: added 1 changesets with 1 changes to 1 files
192 $ cd ../remote
192 $ cd ../remote
193
193
194 check remote tip
194 check remote tip
195
195
196 $ hg tip
196 $ hg tip
197 changeset: 3:a28a9d1a809c
197 changeset: 3:a28a9d1a809c
198 tag: tip
198 tag: tip
199 parent: 0:1160648e36ce
199 parent: 0:1160648e36ce
200 user: test
200 user: test
201 date: Thu Jan 01 00:00:00 1970 +0000
201 date: Thu Jan 01 00:00:00 1970 +0000
202 summary: add
202 summary: add
203
203
204 $ hg verify
204 $ hg verify
205 checking changesets
205 checking changesets
206 checking manifests
206 checking manifests
207 crosschecking files in changesets and manifests
207 crosschecking files in changesets and manifests
208 checking files
208 checking files
209 2 files, 4 changesets, 3 total revisions
209 2 files, 4 changesets, 3 total revisions
210 $ hg cat -r tip foo
210 $ hg cat -r tip foo
211 bleah
211 bleah
212 $ echo z > z
212 $ echo z > z
213 $ hg ci -A -m z z
213 $ hg ci -A -m z z
214 created new head
214 created new head
215
215
216 test pushkeys and bookmarks
216 test pushkeys and bookmarks
217
217
218 $ cd ../local
218 $ cd ../local
219 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
219 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
220 bookmarks
220 bookmarks
221 namespaces
221 namespaces
222 phases
222 phases
223 $ hg book foo -r 0
223 $ hg book foo -r 0
224 $ hg out -B
224 $ hg out -B
225 comparing with ssh://user@dummy/remote
225 comparing with ssh://user@dummy/remote
226 searching for changed bookmarks
226 searching for changed bookmarks
227 foo 1160648e36ce
227 foo 1160648e36ce
228 $ hg push -B foo
228 $ hg push -B foo
229 pushing to ssh://user@dummy/remote
229 pushing to ssh://user@dummy/remote
230 searching for changes
230 searching for changes
231 no changes found
231 no changes found
232 exporting bookmark foo
232 exporting bookmark foo
233 [1]
233 [1]
234 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
234 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
235 foo 1160648e36cec0054048a7edc4110c6f84fde594
235 foo 1160648e36cec0054048a7edc4110c6f84fde594
236 $ hg book -f foo
236 $ hg book -f foo
237 $ hg push --traceback
237 $ hg push --traceback
238 pushing to ssh://user@dummy/remote
238 pushing to ssh://user@dummy/remote
239 searching for changes
239 searching for changes
240 no changes found
240 no changes found
241 updating bookmark foo
241 updating bookmark foo
242 [1]
242 [1]
243 $ hg book -d foo
243 $ hg book -d foo
244 $ hg in -B
244 $ hg in -B
245 comparing with ssh://user@dummy/remote
245 comparing with ssh://user@dummy/remote
246 searching for changed bookmarks
246 searching for changed bookmarks
247 foo a28a9d1a809c
247 foo a28a9d1a809c
248 $ hg book -f -r 0 foo
248 $ hg book -f -r 0 foo
249 $ hg pull -B foo
249 $ hg pull -B foo
250 pulling from ssh://user@dummy/remote
250 pulling from ssh://user@dummy/remote
251 no changes found
251 no changes found
252 updating bookmark foo
252 updating bookmark foo
253 $ hg book -d foo
253 $ hg book -d foo
254 $ hg push -B foo
254 $ hg push -B foo
255 pushing to ssh://user@dummy/remote
255 pushing to ssh://user@dummy/remote
256 searching for changes
256 searching for changes
257 no changes found
257 no changes found
258 deleting remote bookmark foo
258 deleting remote bookmark foo
259 [1]
259 [1]
260
260
261 a bad, evil hook that prints to stdout
261 a bad, evil hook that prints to stdout
262
262
263 $ cat <<EOF > $TESTTMP/badhook
263 $ cat <<EOF > $TESTTMP/badhook
264 > import sys
264 > import sys
265 > sys.stdout.write("KABOOM\n")
265 > sys.stdout.write("KABOOM\n")
266 > EOF
266 > EOF
267
267
268 $ cat <<EOF > $TESTTMP/badpyhook.py
268 $ cat <<EOF > $TESTTMP/badpyhook.py
269 > import sys
269 > import sys
270 > def hook(ui, repo, hooktype, **kwargs):
270 > def hook(ui, repo, hooktype, **kwargs):
271 > sys.stdout.write("KABOOM IN PROCESS\n")
271 > sys.stdout.write("KABOOM IN PROCESS\n")
272 > EOF
272 > EOF
273
273
274 $ cat <<EOF >> ../remote/.hg/hgrc
274 $ cat <<EOF >> ../remote/.hg/hgrc
275 > [hooks]
275 > [hooks]
276 > changegroup.stdout = python $TESTTMP/badhook
276 > changegroup.stdout = python $TESTTMP/badhook
277 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
277 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
278 > EOF
278 > EOF
279 $ echo r > r
279 $ echo r > r
280 $ hg ci -A -m z r
280 $ hg ci -A -m z r
281
281
282 push should succeed even though it has an unexpected response
282 push should succeed even though it has an unexpected response
283
283
284 $ hg push
284 $ hg push
285 pushing to ssh://user@dummy/remote
285 pushing to ssh://user@dummy/remote
286 searching for changes
286 searching for changes
287 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
287 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
288 remote: adding changesets
288 remote: adding changesets
289 remote: adding manifests
289 remote: adding manifests
290 remote: adding file changes
290 remote: adding file changes
291 remote: added 1 changesets with 1 changes to 1 files
291 remote: added 1 changesets with 1 changes to 1 files
292 remote: KABOOM
292 remote: KABOOM
293 remote: KABOOM IN PROCESS
293 remote: KABOOM IN PROCESS
294 $ hg -R ../remote heads
294 $ hg -R ../remote heads
295 changeset: 5:1383141674ec
295 changeset: 5:1383141674ec
296 tag: tip
296 tag: tip
297 parent: 3:a28a9d1a809c
297 parent: 3:a28a9d1a809c
298 user: test
298 user: test
299 date: Thu Jan 01 00:00:00 1970 +0000
299 date: Thu Jan 01 00:00:00 1970 +0000
300 summary: z
300 summary: z
301
301
302 changeset: 4:6c0482d977a3
302 changeset: 4:6c0482d977a3
303 parent: 0:1160648e36ce
303 parent: 0:1160648e36ce
304 user: test
304 user: test
305 date: Thu Jan 01 00:00:00 1970 +0000
305 date: Thu Jan 01 00:00:00 1970 +0000
306 summary: z
306 summary: z
307
307
308
308
309 clone bookmarks
309 clone bookmarks
310
310
311 $ hg -R ../remote bookmark test
311 $ hg -R ../remote bookmark test
312 $ hg -R ../remote bookmarks
312 $ hg -R ../remote bookmarks
313 * test 4:6c0482d977a3
313 * test 4:6c0482d977a3
314 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
314 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
315 requesting all changes
315 requesting all changes
316 adding changesets
316 adding changesets
317 adding manifests
317 adding manifests
318 adding file changes
318 adding file changes
319 added 6 changesets with 5 changes to 4 files (+1 heads)
319 added 6 changesets with 5 changes to 4 files (+1 heads)
320 updating to branch default
320 updating to branch default
321 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 $ hg -R local-bookmarks bookmarks
322 $ hg -R local-bookmarks bookmarks
323 test 4:6c0482d977a3
323 test 4:6c0482d977a3
324
324
325 passwords in ssh urls are not supported
325 passwords in ssh urls are not supported
326 (we use a glob here because different Python versions give different
326 (we use a glob here because different Python versions give different
327 results here)
327 results here)
328
328
329 $ hg push ssh://user:erroneouspwd@dummy/remote
329 $ hg push ssh://user:erroneouspwd@dummy/remote
330 pushing to ssh://user:*@dummy/remote (glob)
330 pushing to ssh://user:*@dummy/remote (glob)
331 abort: password in URL not supported!
331 abort: password in URL not supported!
332 [255]
332 [255]
333
333
334 $ cd ..
334 $ cd ..
335
335
336 hide outer repo
336 hide outer repo
337 $ hg init
337 $ hg init
338
338
339 Test remote paths with spaces (issue2983):
339 Test remote paths with spaces (issue2983):
340
340
341 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
341 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
342 $ touch "$TESTTMP/a repo/test"
342 $ touch "$TESTTMP/a repo/test"
343 $ hg -R 'a repo' commit -A -m "test"
343 $ hg -R 'a repo' commit -A -m "test"
344 adding test
344 adding test
345 $ hg -R 'a repo' tag tag
345 $ hg -R 'a repo' tag tag
346 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
346 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
347 73649e48688a
347 73649e48688a
348
348
349 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
349 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
350 abort: unknown revision 'noNoNO'!
350 abort: unknown revision 'noNoNO'!
351 [255]
351 [255]
352
352
353 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
353 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
354
354
355 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
355 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
356 destination directory: a repo
356 destination directory: a repo
357 abort: destination 'a repo' is not empty
357 abort: destination 'a repo' is not empty
358 [255]
358 [255]
359
359
360 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
360 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
361 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
361 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
362 parameters:
362 parameters:
363
363
364 $ cat > ssh.sh << EOF
364 $ cat > ssh.sh << EOF
365 > userhost="\$1"
365 > userhost="\$1"
366 > SSH_ORIGINAL_COMMAND="\$2"
366 > SSH_ORIGINAL_COMMAND="\$2"
367 > export SSH_ORIGINAL_COMMAND
367 > export SSH_ORIGINAL_COMMAND
368 > PYTHONPATH="$PYTHONPATH"
368 > PYTHONPATH="$PYTHONPATH"
369 > export PYTHONPATH
369 > export PYTHONPATH
370 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
370 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
371 > EOF
371 > EOF
372
372
373 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
373 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
374 73649e48688a
374 73649e48688a
375
375
376 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
376 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
377 remote: Illegal repository "$TESTTMP/a'repo" (glob)
377 remote: Illegal repository "$TESTTMP/a'repo" (glob)
378 abort: no suitable response from remote hg!
378 abort: no suitable response from remote hg!
379 [255]
379 [255]
380
380
381 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
381 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
382 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
382 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
383 abort: no suitable response from remote hg!
383 abort: no suitable response from remote hg!
384 [255]
384 [255]
385
385
386 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
386 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
387 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
387 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
388 [255]
388 [255]
389
389
390 Test hg-ssh in read-only mode:
390 Test hg-ssh in read-only mode:
391
391
392 $ cat > ssh.sh << EOF
392 $ cat > ssh.sh << EOF
393 > userhost="\$1"
393 > userhost="\$1"
394 > SSH_ORIGINAL_COMMAND="\$2"
394 > SSH_ORIGINAL_COMMAND="\$2"
395 > export SSH_ORIGINAL_COMMAND
395 > export SSH_ORIGINAL_COMMAND
396 > PYTHONPATH="$PYTHONPATH"
396 > PYTHONPATH="$PYTHONPATH"
397 > export PYTHONPATH
397 > export PYTHONPATH
398 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
398 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
399 > EOF
399 > EOF
400
400
401 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
401 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
402 requesting all changes
402 requesting all changes
403 adding changesets
403 adding changesets
404 adding manifests
404 adding manifests
405 adding file changes
405 adding file changes
406 added 6 changesets with 5 changes to 4 files (+1 heads)
406 added 6 changesets with 5 changes to 4 files (+1 heads)
407 updating to branch default
407 updating to branch default
408 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
408 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
409
409
410 $ cd read-only-local
410 $ cd read-only-local
411 $ echo "baz" > bar
411 $ echo "baz" > bar
412 $ hg ci -A -m "unpushable commit" bar
412 $ hg ci -A -m "unpushable commit" bar
413 $ hg push --ssh "sh ../ssh.sh"
413 $ hg push --ssh "sh ../ssh.sh"
414 pushing to ssh://user@dummy/*/remote (glob)
414 pushing to ssh://user@dummy/*/remote (glob)
415 searching for changes
415 searching for changes
416 remote: Permission denied
416 remote: Permission denied
417 remote: pretxnopen.hg-ssh hook failed
417 remote: pretxnopen.hg-ssh hook failed
418 abort: push failed on remote
418 abort: push failed on remote
419 [255]
419 [255]
420
420
421 $ cd ..
421 $ cd ..
422
422
423 stderr from remote commands should be printed before stdout from local code (issue4336)
423 stderr from remote commands should be printed before stdout from local code (issue4336)
424
424
425 $ hg clone remote stderr-ordering
425 $ hg clone remote stderr-ordering
426 updating to branch default
426 updating to branch default
427 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 $ cd stderr-ordering
428 $ cd stderr-ordering
429 $ cat >> localwrite.py << EOF
429 $ cat >> localwrite.py << EOF
430 > from mercurial import exchange, extensions
430 > from mercurial import exchange, extensions
431 >
431 >
432 > def wrappedpush(orig, repo, *args, **kwargs):
432 > def wrappedpush(orig, repo, *args, **kwargs):
433 > res = orig(repo, *args, **kwargs)
433 > res = orig(repo, *args, **kwargs)
434 > repo.ui.write('local stdout\n')
434 > repo.ui.write('local stdout\n')
435 > return res
435 > return res
436 >
436 >
437 > def extsetup(ui):
437 > def extsetup(ui):
438 > extensions.wrapfunction(exchange, 'push', wrappedpush)
438 > extensions.wrapfunction(exchange, 'push', wrappedpush)
439 > EOF
439 > EOF
440
440
441 $ cat >> .hg/hgrc << EOF
441 $ cat >> .hg/hgrc << EOF
442 > [paths]
442 > [paths]
443 > default-push = ssh://user@dummy/remote
443 > default-push = ssh://user@dummy/remote
444 > [ui]
444 > [ui]
445 > ssh = python "$TESTDIR/dummyssh"
445 > ssh = python "$TESTDIR/dummyssh"
446 > [extensions]
446 > [extensions]
447 > localwrite = localwrite.py
447 > localwrite = localwrite.py
448 > EOF
448 > EOF
449
449
450 $ echo localwrite > foo
450 $ echo localwrite > foo
451 $ hg commit -m 'testing localwrite'
451 $ hg commit -m 'testing localwrite'
452 $ hg push
452 $ hg push
453 pushing to ssh://user@dummy/remote
453 pushing to ssh://user@dummy/remote
454 searching for changes
454 searching for changes
455 remote: adding changesets
455 remote: adding changesets
456 remote: adding manifests
456 remote: adding manifests
457 remote: adding file changes
457 remote: adding file changes
458 remote: added 1 changesets with 1 changes to 1 files
458 remote: added 1 changesets with 1 changes to 1 files
459 remote: KABOOM
459 remote: KABOOM
460 remote: KABOOM IN PROCESS
460 remote: KABOOM IN PROCESS
461 local stdout
461 local stdout
462
462
463 debug output
463 debug output
464
464
465 $ hg pull --debug ssh://user@dummy/remote
465 $ hg pull --debug ssh://user@dummy/remote
466 pulling from ssh://user@dummy/remote
466 pulling from ssh://user@dummy/remote
467 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
467 running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
468 sending hello command
468 sending hello command
469 sending between command
469 sending between command
470 remote: 371
470 remote: 355
471 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
471 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
472 remote: 1
472 remote: 1
473 query 1; heads
473 query 1; heads
474 sending batch command
474 sending batch command
475 searching for changes
475 searching for changes
476 all remote heads known locally
476 all remote heads known locally
477 no changes found
477 no changes found
478 sending getbundle command
478 sending getbundle command
479 bundle2-input-bundle: with-transaction
479 bundle2-input-bundle: with-transaction
480 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
480 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
481 bundle2-input-part: total payload size 15
481 bundle2-input-part: total payload size 15
482 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
482 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
483 bundle2-input-part: total payload size 45
483 bundle2-input-part: total payload size 45
484 bundle2-input-bundle: 1 parts total
484 bundle2-input-bundle: 1 parts total
485 checking for updated bookmarks
485 checking for updated bookmarks
486
486
487 $ cd ..
487 $ cd ..
488
488
489 $ cat dummylog
489 $ cat dummylog
490 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
490 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
491 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
491 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
492 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
492 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
493 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
493 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
494 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
494 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
495 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
495 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
496 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
496 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
497 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
497 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
498 Got arguments 1:user@dummy 2:hg -R local serve --stdio
498 Got arguments 1:user@dummy 2:hg -R local serve --stdio
499 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
499 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
500 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
501 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
501 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
502 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
504 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
506 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
508 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
509 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
511 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
511 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
512 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
512 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
513 Got arguments 1:user@dummy 2:hg init 'a repo'
513 Got arguments 1:user@dummy 2:hg init 'a repo'
514 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
514 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
515 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
515 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
516 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
516 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
517 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
517 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
519 changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521
521
522 remote hook failure is attributed to remote
522 remote hook failure is attributed to remote
523
523
524 $ cat > $TESTTMP/failhook << EOF
524 $ cat > $TESTTMP/failhook << EOF
525 > def hook(ui, repo, **kwargs):
525 > def hook(ui, repo, **kwargs):
526 > ui.write('hook failure!\n')
526 > ui.write('hook failure!\n')
527 > ui.flush()
527 > ui.flush()
528 > return 1
528 > return 1
529 > EOF
529 > EOF
530
530
531 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
531 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
532
532
533 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
533 $ hg -q --config ui.ssh="python $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
534 $ cd hookout
534 $ cd hookout
535 $ touch hookfailure
535 $ touch hookfailure
536 $ hg -q commit -A -m 'remote hook failure'
536 $ hg -q commit -A -m 'remote hook failure'
537 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
537 $ hg --config ui.ssh="python $TESTDIR/dummyssh" push
538 pushing to ssh://user@dummy/remote
538 pushing to ssh://user@dummy/remote
539 searching for changes
539 searching for changes
540 remote: adding changesets
540 remote: adding changesets
541 remote: adding manifests
541 remote: adding manifests
542 remote: adding file changes
542 remote: adding file changes
543 remote: added 1 changesets with 1 changes to 1 files
543 remote: added 1 changesets with 1 changes to 1 files
544 remote: hook failure!
544 remote: hook failure!
545 remote: transaction abort!
545 remote: transaction abort!
546 remote: rollback completed
546 remote: rollback completed
547 remote: pretxnchangegroup.fail hook failed
547 remote: pretxnchangegroup.fail hook failed
548 abort: push failed on remote
548 abort: push failed on remote
549 [255]
549 [255]
550
550
General Comments 0
You need to be logged in to leave comments. Login now