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