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