##// END OF EJS Templates
bundle2: allow bundle2 for pulling over the wire...
Pierre-Yves David -
r21069:0a9cae23 default
parent child Browse files
Show More
@@ -1,785 +1,790 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
11 import changegroup as changegroupmod, bundle2
12 import peer, error, encoding, util, store, exchange
12 import peer, error, encoding, util, store, 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 return sep.join(map(hex, l))
175 return sep.join(map(hex, l))
176
176
177 # batched call argument encoding
177 # batched call argument encoding
178
178
179 def escapearg(plain):
179 def escapearg(plain):
180 return (plain
180 return (plain
181 .replace(':', '::')
181 .replace(':', '::')
182 .replace(',', ':,')
182 .replace(',', ':,')
183 .replace(';', ':;')
183 .replace(';', ':;')
184 .replace('=', ':='))
184 .replace('=', ':='))
185
185
186 def unescapearg(escaped):
186 def unescapearg(escaped):
187 return (escaped
187 return (escaped
188 .replace(':=', '=')
188 .replace(':=', '=')
189 .replace(':;', ';')
189 .replace(':;', ';')
190 .replace(':,', ',')
190 .replace(':,', ',')
191 .replace('::', ':'))
191 .replace('::', ':'))
192
192
193 # client side
193 # client side
194
194
195 class wirepeer(peer.peerrepository):
195 class wirepeer(peer.peerrepository):
196
196
197 def batch(self):
197 def batch(self):
198 return remotebatch(self)
198 return remotebatch(self)
199 def _submitbatch(self, req):
199 def _submitbatch(self, req):
200 cmds = []
200 cmds = []
201 for op, argsdict in req:
201 for op, argsdict in req:
202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
203 cmds.append('%s %s' % (op, args))
203 cmds.append('%s %s' % (op, args))
204 rsp = self._call("batch", cmds=';'.join(cmds))
204 rsp = self._call("batch", cmds=';'.join(cmds))
205 return rsp.split(';')
205 return rsp.split(';')
206 def _submitone(self, op, args):
206 def _submitone(self, op, args):
207 return self._call(op, **args)
207 return self._call(op, **args)
208
208
209 @batchable
209 @batchable
210 def lookup(self, key):
210 def lookup(self, key):
211 self.requirecap('lookup', _('look up remote revision'))
211 self.requirecap('lookup', _('look up remote revision'))
212 f = future()
212 f = future()
213 yield {'key': encoding.fromlocal(key)}, f
213 yield {'key': encoding.fromlocal(key)}, f
214 d = f.value
214 d = f.value
215 success, data = d[:-1].split(" ", 1)
215 success, data = d[:-1].split(" ", 1)
216 if int(success):
216 if int(success):
217 yield bin(data)
217 yield bin(data)
218 self._abort(error.RepoError(data))
218 self._abort(error.RepoError(data))
219
219
220 @batchable
220 @batchable
221 def heads(self):
221 def heads(self):
222 f = future()
222 f = future()
223 yield {}, f
223 yield {}, f
224 d = f.value
224 d = f.value
225 try:
225 try:
226 yield decodelist(d[:-1])
226 yield decodelist(d[:-1])
227 except ValueError:
227 except ValueError:
228 self._abort(error.ResponseError(_("unexpected response:"), d))
228 self._abort(error.ResponseError(_("unexpected response:"), d))
229
229
230 @batchable
230 @batchable
231 def known(self, nodes):
231 def known(self, nodes):
232 f = future()
232 f = future()
233 yield {'nodes': encodelist(nodes)}, f
233 yield {'nodes': encodelist(nodes)}, f
234 d = f.value
234 d = f.value
235 try:
235 try:
236 yield [bool(int(f)) for f in d]
236 yield [bool(int(f)) for f in d]
237 except ValueError:
237 except ValueError:
238 self._abort(error.ResponseError(_("unexpected response:"), d))
238 self._abort(error.ResponseError(_("unexpected response:"), d))
239
239
240 @batchable
240 @batchable
241 def branchmap(self):
241 def branchmap(self):
242 f = future()
242 f = future()
243 yield {}, f
243 yield {}, f
244 d = f.value
244 d = f.value
245 try:
245 try:
246 branchmap = {}
246 branchmap = {}
247 for branchpart in d.splitlines():
247 for branchpart in d.splitlines():
248 branchname, branchheads = branchpart.split(' ', 1)
248 branchname, branchheads = branchpart.split(' ', 1)
249 branchname = encoding.tolocal(urllib.unquote(branchname))
249 branchname = encoding.tolocal(urllib.unquote(branchname))
250 branchheads = decodelist(branchheads)
250 branchheads = decodelist(branchheads)
251 branchmap[branchname] = branchheads
251 branchmap[branchname] = branchheads
252 yield branchmap
252 yield branchmap
253 except TypeError:
253 except TypeError:
254 self._abort(error.ResponseError(_("unexpected response:"), d))
254 self._abort(error.ResponseError(_("unexpected response:"), d))
255
255
256 def branches(self, nodes):
256 def branches(self, nodes):
257 n = encodelist(nodes)
257 n = encodelist(nodes)
258 d = self._call("branches", nodes=n)
258 d = self._call("branches", nodes=n)
259 try:
259 try:
260 br = [tuple(decodelist(b)) for b in d.splitlines()]
260 br = [tuple(decodelist(b)) for b in d.splitlines()]
261 return br
261 return br
262 except ValueError:
262 except ValueError:
263 self._abort(error.ResponseError(_("unexpected response:"), d))
263 self._abort(error.ResponseError(_("unexpected response:"), d))
264
264
265 def between(self, pairs):
265 def between(self, pairs):
266 batch = 8 # avoid giant requests
266 batch = 8 # avoid giant requests
267 r = []
267 r = []
268 for i in xrange(0, len(pairs), batch):
268 for i in xrange(0, len(pairs), batch):
269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
270 d = self._call("between", pairs=n)
270 d = self._call("between", pairs=n)
271 try:
271 try:
272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
273 except ValueError:
273 except ValueError:
274 self._abort(error.ResponseError(_("unexpected response:"), d))
274 self._abort(error.ResponseError(_("unexpected response:"), d))
275 return r
275 return r
276
276
277 @batchable
277 @batchable
278 def pushkey(self, namespace, key, old, new):
278 def pushkey(self, namespace, key, old, new):
279 if not self.capable('pushkey'):
279 if not self.capable('pushkey'):
280 yield False, None
280 yield False, None
281 f = future()
281 f = future()
282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
283 yield {'namespace': encoding.fromlocal(namespace),
283 yield {'namespace': encoding.fromlocal(namespace),
284 'key': encoding.fromlocal(key),
284 'key': encoding.fromlocal(key),
285 'old': encoding.fromlocal(old),
285 'old': encoding.fromlocal(old),
286 'new': encoding.fromlocal(new)}, f
286 'new': encoding.fromlocal(new)}, f
287 d = f.value
287 d = f.value
288 d, output = d.split('\n', 1)
288 d, output = d.split('\n', 1)
289 try:
289 try:
290 d = bool(int(d))
290 d = bool(int(d))
291 except ValueError:
291 except ValueError:
292 raise error.ResponseError(
292 raise error.ResponseError(
293 _('push failed (unexpected response):'), d)
293 _('push failed (unexpected response):'), d)
294 for l in output.splitlines(True):
294 for l in output.splitlines(True):
295 self.ui.status(_('remote: '), l)
295 self.ui.status(_('remote: '), l)
296 yield d
296 yield d
297
297
298 @batchable
298 @batchable
299 def listkeys(self, namespace):
299 def listkeys(self, namespace):
300 if not self.capable('pushkey'):
300 if not self.capable('pushkey'):
301 yield {}, None
301 yield {}, None
302 f = future()
302 f = future()
303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
304 yield {'namespace': encoding.fromlocal(namespace)}, f
304 yield {'namespace': encoding.fromlocal(namespace)}, f
305 d = f.value
305 d = f.value
306 r = {}
306 r = {}
307 for l in d.splitlines():
307 for l in d.splitlines():
308 k, v = l.split('\t')
308 k, v = l.split('\t')
309 r[encoding.tolocal(k)] = encoding.tolocal(v)
309 r[encoding.tolocal(k)] = encoding.tolocal(v)
310 yield r
310 yield r
311
311
312 def stream_out(self):
312 def stream_out(self):
313 return self._callstream('stream_out')
313 return self._callstream('stream_out')
314
314
315 def changegroup(self, nodes, kind):
315 def changegroup(self, nodes, kind):
316 n = encodelist(nodes)
316 n = encodelist(nodes)
317 f = self._callcompressable("changegroup", roots=n)
317 f = self._callcompressable("changegroup", roots=n)
318 return changegroupmod.unbundle10(f, 'UN')
318 return changegroupmod.unbundle10(f, 'UN')
319
319
320 def changegroupsubset(self, bases, heads, kind):
320 def changegroupsubset(self, bases, heads, kind):
321 self.requirecap('changegroupsubset', _('look up remote changes'))
321 self.requirecap('changegroupsubset', _('look up remote changes'))
322 bases = encodelist(bases)
322 bases = encodelist(bases)
323 heads = encodelist(heads)
323 heads = encodelist(heads)
324 f = self._callcompressable("changegroupsubset",
324 f = self._callcompressable("changegroupsubset",
325 bases=bases, heads=heads)
325 bases=bases, heads=heads)
326 return changegroupmod.unbundle10(f, 'UN')
326 return changegroupmod.unbundle10(f, 'UN')
327
327
328 def getbundle(self, source, heads=None, common=None, bundlecaps=None):
328 def getbundle(self, source, heads=None, common=None, bundlecaps=None):
329 self.requirecap('getbundle', _('look up remote changes'))
329 self.requirecap('getbundle', _('look up remote changes'))
330 opts = {}
330 opts = {}
331 if heads is not None:
331 if heads is not None:
332 opts['heads'] = encodelist(heads)
332 opts['heads'] = encodelist(heads)
333 if common is not None:
333 if common is not None:
334 opts['common'] = encodelist(common)
334 opts['common'] = encodelist(common)
335 if bundlecaps is not None:
335 if bundlecaps is not None:
336 opts['bundlecaps'] = ','.join(bundlecaps)
336 opts['bundlecaps'] = ','.join(bundlecaps)
337 f = self._callcompressable("getbundle", **opts)
337 f = self._callcompressable("getbundle", **opts)
338 return changegroupmod.unbundle10(f, 'UN')
338 if bundlecaps is not None and 'HG20' in bundlecaps:
339 return bundle2.unbundle20(self.ui, f)
340 else:
341 return changegroupmod.unbundle10(f, 'UN')
339
342
340 def unbundle(self, cg, heads, source):
343 def unbundle(self, cg, heads, source):
341 '''Send cg (a readable file-like object representing the
344 '''Send cg (a readable file-like object representing the
342 changegroup to push, typically a chunkbuffer object) to the
345 changegroup to push, typically a chunkbuffer object) to the
343 remote server as a bundle. Return an integer indicating the
346 remote server as a bundle. Return an integer indicating the
344 result of the push (see localrepository.addchangegroup()).'''
347 result of the push (see localrepository.addchangegroup()).'''
345
348
346 if heads != ['force'] and self.capable('unbundlehash'):
349 if heads != ['force'] and self.capable('unbundlehash'):
347 heads = encodelist(['hashed',
350 heads = encodelist(['hashed',
348 util.sha1(''.join(sorted(heads))).digest()])
351 util.sha1(''.join(sorted(heads))).digest()])
349 else:
352 else:
350 heads = encodelist(heads)
353 heads = encodelist(heads)
351
354
352 ret, output = self._callpush("unbundle", cg, heads=heads)
355 ret, output = self._callpush("unbundle", cg, heads=heads)
353 if ret == "":
356 if ret == "":
354 raise error.ResponseError(
357 raise error.ResponseError(
355 _('push failed:'), output)
358 _('push failed:'), output)
356 try:
359 try:
357 ret = int(ret)
360 ret = int(ret)
358 except ValueError:
361 except ValueError:
359 raise error.ResponseError(
362 raise error.ResponseError(
360 _('push failed (unexpected response):'), ret)
363 _('push failed (unexpected response):'), ret)
361
364
362 for l in output.splitlines(True):
365 for l in output.splitlines(True):
363 self.ui.status(_('remote: '), l)
366 self.ui.status(_('remote: '), l)
364 return ret
367 return ret
365
368
366 def debugwireargs(self, one, two, three=None, four=None, five=None):
369 def debugwireargs(self, one, two, three=None, four=None, five=None):
367 # don't pass optional arguments left at their default value
370 # don't pass optional arguments left at their default value
368 opts = {}
371 opts = {}
369 if three is not None:
372 if three is not None:
370 opts['three'] = three
373 opts['three'] = three
371 if four is not None:
374 if four is not None:
372 opts['four'] = four
375 opts['four'] = four
373 return self._call('debugwireargs', one=one, two=two, **opts)
376 return self._call('debugwireargs', one=one, two=two, **opts)
374
377
375 def _call(self, cmd, **args):
378 def _call(self, cmd, **args):
376 """execute <cmd> on the server
379 """execute <cmd> on the server
377
380
378 The command is expected to return a simple string.
381 The command is expected to return a simple string.
379
382
380 returns the server reply as a string."""
383 returns the server reply as a string."""
381 raise NotImplementedError()
384 raise NotImplementedError()
382
385
383 def _callstream(self, cmd, **args):
386 def _callstream(self, cmd, **args):
384 """execute <cmd> on the server
387 """execute <cmd> on the server
385
388
386 The command is expected to return a stream.
389 The command is expected to return a stream.
387
390
388 returns the server reply as a file like object."""
391 returns the server reply as a file like object."""
389 raise NotImplementedError()
392 raise NotImplementedError()
390
393
391 def _callcompressable(self, cmd, **args):
394 def _callcompressable(self, cmd, **args):
392 """execute <cmd> on the server
395 """execute <cmd> on the server
393
396
394 The command is expected to return a stream.
397 The command is expected to return a stream.
395
398
396 The stream may have been compressed in some implementations. This
399 The stream may have been compressed in some implementations. This
397 function takes care of the decompression. This is the only difference
400 function takes care of the decompression. This is the only difference
398 with _callstream.
401 with _callstream.
399
402
400 returns the server reply as a file like object.
403 returns the server reply as a file like object.
401 """
404 """
402 raise NotImplementedError()
405 raise NotImplementedError()
403
406
404 def _callpush(self, cmd, fp, **args):
407 def _callpush(self, cmd, fp, **args):
405 """execute a <cmd> on server
408 """execute a <cmd> on server
406
409
407 The command is expected to be related to a push. Push has a special
410 The command is expected to be related to a push. Push has a special
408 return method.
411 return method.
409
412
410 returns the server reply as a (ret, output) tuple. ret is either
413 returns the server reply as a (ret, output) tuple. ret is either
411 empty (error) or a stringified int.
414 empty (error) or a stringified int.
412 """
415 """
413 raise NotImplementedError()
416 raise NotImplementedError()
414
417
415 def _abort(self, exception):
418 def _abort(self, exception):
416 """clearly abort the wire protocol connection and raise the exception
419 """clearly abort the wire protocol connection and raise the exception
417 """
420 """
418 raise NotImplementedError()
421 raise NotImplementedError()
419
422
420 # server side
423 # server side
421
424
422 # wire protocol command can either return a string or one of these classes.
425 # wire protocol command can either return a string or one of these classes.
423 class streamres(object):
426 class streamres(object):
424 """wireproto reply: binary stream
427 """wireproto reply: binary stream
425
428
426 The call was successful and the result is a stream.
429 The call was successful and the result is a stream.
427 Iterate on the `self.gen` attribute to retrieve chunks.
430 Iterate on the `self.gen` attribute to retrieve chunks.
428 """
431 """
429 def __init__(self, gen):
432 def __init__(self, gen):
430 self.gen = gen
433 self.gen = gen
431
434
432 class pushres(object):
435 class pushres(object):
433 """wireproto reply: success with simple integer return
436 """wireproto reply: success with simple integer return
434
437
435 The call was successful and returned an integer contained in `self.res`.
438 The call was successful and returned an integer contained in `self.res`.
436 """
439 """
437 def __init__(self, res):
440 def __init__(self, res):
438 self.res = res
441 self.res = res
439
442
440 class pusherr(object):
443 class pusherr(object):
441 """wireproto reply: failure
444 """wireproto reply: failure
442
445
443 The call failed. The `self.res` attribute contains the error message.
446 The call failed. The `self.res` attribute contains the error message.
444 """
447 """
445 def __init__(self, res):
448 def __init__(self, res):
446 self.res = res
449 self.res = res
447
450
448 class ooberror(object):
451 class ooberror(object):
449 """wireproto reply: failure of a batch of operation
452 """wireproto reply: failure of a batch of operation
450
453
451 Something failed during a batch call. The error message is stored in
454 Something failed during a batch call. The error message is stored in
452 `self.message`.
455 `self.message`.
453 """
456 """
454 def __init__(self, message):
457 def __init__(self, message):
455 self.message = message
458 self.message = message
456
459
457 def dispatch(repo, proto, command):
460 def dispatch(repo, proto, command):
458 repo = repo.filtered("served")
461 repo = repo.filtered("served")
459 func, spec = commands[command]
462 func, spec = commands[command]
460 args = proto.getargs(spec)
463 args = proto.getargs(spec)
461 return func(repo, proto, *args)
464 return func(repo, proto, *args)
462
465
463 def options(cmd, keys, others):
466 def options(cmd, keys, others):
464 opts = {}
467 opts = {}
465 for k in keys:
468 for k in keys:
466 if k in others:
469 if k in others:
467 opts[k] = others[k]
470 opts[k] = others[k]
468 del others[k]
471 del others[k]
469 if others:
472 if others:
470 sys.stderr.write("abort: %s got unexpected arguments %s\n"
473 sys.stderr.write("abort: %s got unexpected arguments %s\n"
471 % (cmd, ",".join(others)))
474 % (cmd, ",".join(others)))
472 return opts
475 return opts
473
476
474 # list of commands
477 # list of commands
475 commands = {}
478 commands = {}
476
479
477 def wireprotocommand(name, args=''):
480 def wireprotocommand(name, args=''):
478 """decorator for wire protocol command"""
481 """decorator for wire protocol command"""
479 def register(func):
482 def register(func):
480 commands[name] = (func, args)
483 commands[name] = (func, args)
481 return func
484 return func
482 return register
485 return register
483
486
484 @wireprotocommand('batch', 'cmds *')
487 @wireprotocommand('batch', 'cmds *')
485 def batch(repo, proto, cmds, others):
488 def batch(repo, proto, cmds, others):
486 repo = repo.filtered("served")
489 repo = repo.filtered("served")
487 res = []
490 res = []
488 for pair in cmds.split(';'):
491 for pair in cmds.split(';'):
489 op, args = pair.split(' ', 1)
492 op, args = pair.split(' ', 1)
490 vals = {}
493 vals = {}
491 for a in args.split(','):
494 for a in args.split(','):
492 if a:
495 if a:
493 n, v = a.split('=')
496 n, v = a.split('=')
494 vals[n] = unescapearg(v)
497 vals[n] = unescapearg(v)
495 func, spec = commands[op]
498 func, spec = commands[op]
496 if spec:
499 if spec:
497 keys = spec.split()
500 keys = spec.split()
498 data = {}
501 data = {}
499 for k in keys:
502 for k in keys:
500 if k == '*':
503 if k == '*':
501 star = {}
504 star = {}
502 for key in vals.keys():
505 for key in vals.keys():
503 if key not in keys:
506 if key not in keys:
504 star[key] = vals[key]
507 star[key] = vals[key]
505 data['*'] = star
508 data['*'] = star
506 else:
509 else:
507 data[k] = vals[k]
510 data[k] = vals[k]
508 result = func(repo, proto, *[data[k] for k in keys])
511 result = func(repo, proto, *[data[k] for k in keys])
509 else:
512 else:
510 result = func(repo, proto)
513 result = func(repo, proto)
511 if isinstance(result, ooberror):
514 if isinstance(result, ooberror):
512 return result
515 return result
513 res.append(escapearg(result))
516 res.append(escapearg(result))
514 return ';'.join(res)
517 return ';'.join(res)
515
518
516 @wireprotocommand('between', 'pairs')
519 @wireprotocommand('between', 'pairs')
517 def between(repo, proto, pairs):
520 def between(repo, proto, pairs):
518 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
521 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
519 r = []
522 r = []
520 for b in repo.between(pairs):
523 for b in repo.between(pairs):
521 r.append(encodelist(b) + "\n")
524 r.append(encodelist(b) + "\n")
522 return "".join(r)
525 return "".join(r)
523
526
524 @wireprotocommand('branchmap')
527 @wireprotocommand('branchmap')
525 def branchmap(repo, proto):
528 def branchmap(repo, proto):
526 branchmap = repo.branchmap()
529 branchmap = repo.branchmap()
527 heads = []
530 heads = []
528 for branch, nodes in branchmap.iteritems():
531 for branch, nodes in branchmap.iteritems():
529 branchname = urllib.quote(encoding.fromlocal(branch))
532 branchname = urllib.quote(encoding.fromlocal(branch))
530 branchnodes = encodelist(nodes)
533 branchnodes = encodelist(nodes)
531 heads.append('%s %s' % (branchname, branchnodes))
534 heads.append('%s %s' % (branchname, branchnodes))
532 return '\n'.join(heads)
535 return '\n'.join(heads)
533
536
534 @wireprotocommand('branches', 'nodes')
537 @wireprotocommand('branches', 'nodes')
535 def branches(repo, proto, nodes):
538 def branches(repo, proto, nodes):
536 nodes = decodelist(nodes)
539 nodes = decodelist(nodes)
537 r = []
540 r = []
538 for b in repo.branches(nodes):
541 for b in repo.branches(nodes):
539 r.append(encodelist(b) + "\n")
542 r.append(encodelist(b) + "\n")
540 return "".join(r)
543 return "".join(r)
541
544
542
545
543 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
546 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
544 'known', 'getbundle', 'unbundlehash', 'batch']
547 'known', 'getbundle', 'unbundlehash', 'batch']
545
548
546 def _capabilities(repo, proto):
549 def _capabilities(repo, proto):
547 """return a list of capabilities for a repo
550 """return a list of capabilities for a repo
548
551
549 This function exists to allow extensions to easily wrap capabilities
552 This function exists to allow extensions to easily wrap capabilities
550 computation
553 computation
551
554
552 - returns a lists: easy to alter
555 - returns a lists: easy to alter
553 - change done here will be propagated to both `capabilities` and `hello`
556 - change done here will be propagated to both `capabilities` and `hello`
554 command without any other action needed.
557 command without any other action needed.
555 """
558 """
556 # copy to prevent modification of the global list
559 # copy to prevent modification of the global list
557 caps = list(wireprotocaps)
560 caps = list(wireprotocaps)
558 if _allowstream(repo.ui):
561 if _allowstream(repo.ui):
559 if repo.ui.configbool('server', 'preferuncompressed', False):
562 if repo.ui.configbool('server', 'preferuncompressed', False):
560 caps.append('stream-preferred')
563 caps.append('stream-preferred')
561 requiredformats = repo.requirements & repo.supportedformats
564 requiredformats = repo.requirements & repo.supportedformats
562 # if our local revlogs are just revlogv1, add 'stream' cap
565 # if our local revlogs are just revlogv1, add 'stream' cap
563 if not requiredformats - set(('revlogv1',)):
566 if not requiredformats - set(('revlogv1',)):
564 caps.append('stream')
567 caps.append('stream')
565 # otherwise, add 'streamreqs' detailing our local revlog format
568 # otherwise, add 'streamreqs' detailing our local revlog format
566 else:
569 else:
567 caps.append('streamreqs=%s' % ','.join(requiredformats))
570 caps.append('streamreqs=%s' % ','.join(requiredformats))
571 if repo.ui.configbool('server', 'bundle2', False):
572 caps.append('bundle2')
568 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
573 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
569 caps.append('httpheader=1024')
574 caps.append('httpheader=1024')
570 return caps
575 return caps
571
576
572 # If you are writing an extension and consider wrapping this function. Wrap
577 # If you are writing an extension and consider wrapping this function. Wrap
573 # `_capabilities` instead.
578 # `_capabilities` instead.
574 @wireprotocommand('capabilities')
579 @wireprotocommand('capabilities')
575 def capabilities(repo, proto):
580 def capabilities(repo, proto):
576 return ' '.join(_capabilities(repo, proto))
581 return ' '.join(_capabilities(repo, proto))
577
582
578 @wireprotocommand('changegroup', 'roots')
583 @wireprotocommand('changegroup', 'roots')
579 def changegroup(repo, proto, roots):
584 def changegroup(repo, proto, roots):
580 nodes = decodelist(roots)
585 nodes = decodelist(roots)
581 cg = changegroupmod.changegroup(repo, nodes, 'serve')
586 cg = changegroupmod.changegroup(repo, nodes, 'serve')
582 return streamres(proto.groupchunks(cg))
587 return streamres(proto.groupchunks(cg))
583
588
584 @wireprotocommand('changegroupsubset', 'bases heads')
589 @wireprotocommand('changegroupsubset', 'bases heads')
585 def changegroupsubset(repo, proto, bases, heads):
590 def changegroupsubset(repo, proto, bases, heads):
586 bases = decodelist(bases)
591 bases = decodelist(bases)
587 heads = decodelist(heads)
592 heads = decodelist(heads)
588 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
593 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
589 return streamres(proto.groupchunks(cg))
594 return streamres(proto.groupchunks(cg))
590
595
591 @wireprotocommand('debugwireargs', 'one two *')
596 @wireprotocommand('debugwireargs', 'one two *')
592 def debugwireargs(repo, proto, one, two, others):
597 def debugwireargs(repo, proto, one, two, others):
593 # only accept optional args from the known set
598 # only accept optional args from the known set
594 opts = options('debugwireargs', ['three', 'four'], others)
599 opts = options('debugwireargs', ['three', 'four'], others)
595 return repo.debugwireargs(one, two, **opts)
600 return repo.debugwireargs(one, two, **opts)
596
601
597 @wireprotocommand('getbundle', '*')
602 @wireprotocommand('getbundle', '*')
598 def getbundle(repo, proto, others):
603 def getbundle(repo, proto, others):
599 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
604 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
600 for k, v in opts.iteritems():
605 for k, v in opts.iteritems():
601 if k in ('heads', 'common'):
606 if k in ('heads', 'common'):
602 opts[k] = decodelist(v)
607 opts[k] = decodelist(v)
603 elif k == 'bundlecaps':
608 elif k == 'bundlecaps':
604 opts[k] = set(v.split(','))
609 opts[k] = set(v.split(','))
605 cg = changegroupmod.getbundle(repo, 'serve', **opts)
610 cg = exchange.getbundle(repo, 'serve', **opts)
606 return streamres(proto.groupchunks(cg))
611 return streamres(proto.groupchunks(cg))
607
612
608 @wireprotocommand('heads')
613 @wireprotocommand('heads')
609 def heads(repo, proto):
614 def heads(repo, proto):
610 h = repo.heads()
615 h = repo.heads()
611 return encodelist(h) + "\n"
616 return encodelist(h) + "\n"
612
617
613 @wireprotocommand('hello')
618 @wireprotocommand('hello')
614 def hello(repo, proto):
619 def hello(repo, proto):
615 '''the hello command returns a set of lines describing various
620 '''the hello command returns a set of lines describing various
616 interesting things about the server, in an RFC822-like format.
621 interesting things about the server, in an RFC822-like format.
617 Currently the only one defined is "capabilities", which
622 Currently the only one defined is "capabilities", which
618 consists of a line in the form:
623 consists of a line in the form:
619
624
620 capabilities: space separated list of tokens
625 capabilities: space separated list of tokens
621 '''
626 '''
622 return "capabilities: %s\n" % (capabilities(repo, proto))
627 return "capabilities: %s\n" % (capabilities(repo, proto))
623
628
624 @wireprotocommand('listkeys', 'namespace')
629 @wireprotocommand('listkeys', 'namespace')
625 def listkeys(repo, proto, namespace):
630 def listkeys(repo, proto, namespace):
626 d = repo.listkeys(encoding.tolocal(namespace)).items()
631 d = repo.listkeys(encoding.tolocal(namespace)).items()
627 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
632 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
628 for k, v in d])
633 for k, v in d])
629 return t
634 return t
630
635
631 @wireprotocommand('lookup', 'key')
636 @wireprotocommand('lookup', 'key')
632 def lookup(repo, proto, key):
637 def lookup(repo, proto, key):
633 try:
638 try:
634 k = encoding.tolocal(key)
639 k = encoding.tolocal(key)
635 c = repo[k]
640 c = repo[k]
636 r = c.hex()
641 r = c.hex()
637 success = 1
642 success = 1
638 except Exception, inst:
643 except Exception, inst:
639 r = str(inst)
644 r = str(inst)
640 success = 0
645 success = 0
641 return "%s %s\n" % (success, r)
646 return "%s %s\n" % (success, r)
642
647
643 @wireprotocommand('known', 'nodes *')
648 @wireprotocommand('known', 'nodes *')
644 def known(repo, proto, nodes, others):
649 def known(repo, proto, nodes, others):
645 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
650 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
646
651
647 @wireprotocommand('pushkey', 'namespace key old new')
652 @wireprotocommand('pushkey', 'namespace key old new')
648 def pushkey(repo, proto, namespace, key, old, new):
653 def pushkey(repo, proto, namespace, key, old, new):
649 # compatibility with pre-1.8 clients which were accidentally
654 # compatibility with pre-1.8 clients which were accidentally
650 # sending raw binary nodes rather than utf-8-encoded hex
655 # sending raw binary nodes rather than utf-8-encoded hex
651 if len(new) == 20 and new.encode('string-escape') != new:
656 if len(new) == 20 and new.encode('string-escape') != new:
652 # looks like it could be a binary node
657 # looks like it could be a binary node
653 try:
658 try:
654 new.decode('utf-8')
659 new.decode('utf-8')
655 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
660 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
656 except UnicodeDecodeError:
661 except UnicodeDecodeError:
657 pass # binary, leave unmodified
662 pass # binary, leave unmodified
658 else:
663 else:
659 new = encoding.tolocal(new) # normal path
664 new = encoding.tolocal(new) # normal path
660
665
661 if util.safehasattr(proto, 'restore'):
666 if util.safehasattr(proto, 'restore'):
662
667
663 proto.redirect()
668 proto.redirect()
664
669
665 try:
670 try:
666 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
671 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
667 encoding.tolocal(old), new) or False
672 encoding.tolocal(old), new) or False
668 except util.Abort:
673 except util.Abort:
669 r = False
674 r = False
670
675
671 output = proto.restore()
676 output = proto.restore()
672
677
673 return '%s\n%s' % (int(r), output)
678 return '%s\n%s' % (int(r), output)
674
679
675 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
680 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
676 encoding.tolocal(old), new)
681 encoding.tolocal(old), new)
677 return '%s\n' % int(r)
682 return '%s\n' % int(r)
678
683
679 def _allowstream(ui):
684 def _allowstream(ui):
680 return ui.configbool('server', 'uncompressed', True, untrusted=True)
685 return ui.configbool('server', 'uncompressed', True, untrusted=True)
681
686
682 def _walkstreamfiles(repo):
687 def _walkstreamfiles(repo):
683 # this is it's own function so extensions can override it
688 # this is it's own function so extensions can override it
684 return repo.store.walk()
689 return repo.store.walk()
685
690
686 @wireprotocommand('stream_out')
691 @wireprotocommand('stream_out')
687 def stream(repo, proto):
692 def stream(repo, proto):
688 '''If the server supports streaming clone, it advertises the "stream"
693 '''If the server supports streaming clone, it advertises the "stream"
689 capability with a value representing the version and flags of the repo
694 capability with a value representing the version and flags of the repo
690 it is serving. Client checks to see if it understands the format.
695 it is serving. Client checks to see if it understands the format.
691
696
692 The format is simple: the server writes out a line with the amount
697 The format is simple: the server writes out a line with the amount
693 of files, then the total amount of bytes to be transferred (separated
698 of files, then the total amount of bytes to be transferred (separated
694 by a space). Then, for each file, the server first writes the filename
699 by a space). Then, for each file, the server first writes the filename
695 and file size (separated by the null character), then the file contents.
700 and file size (separated by the null character), then the file contents.
696 '''
701 '''
697
702
698 if not _allowstream(repo.ui):
703 if not _allowstream(repo.ui):
699 return '1\n'
704 return '1\n'
700
705
701 entries = []
706 entries = []
702 total_bytes = 0
707 total_bytes = 0
703 try:
708 try:
704 # get consistent snapshot of repo, lock during scan
709 # get consistent snapshot of repo, lock during scan
705 lock = repo.lock()
710 lock = repo.lock()
706 try:
711 try:
707 repo.ui.debug('scanning\n')
712 repo.ui.debug('scanning\n')
708 for name, ename, size in _walkstreamfiles(repo):
713 for name, ename, size in _walkstreamfiles(repo):
709 if size:
714 if size:
710 entries.append((name, size))
715 entries.append((name, size))
711 total_bytes += size
716 total_bytes += size
712 finally:
717 finally:
713 lock.release()
718 lock.release()
714 except error.LockError:
719 except error.LockError:
715 return '2\n' # error: 2
720 return '2\n' # error: 2
716
721
717 def streamer(repo, entries, total):
722 def streamer(repo, entries, total):
718 '''stream out all metadata files in repository.'''
723 '''stream out all metadata files in repository.'''
719 yield '0\n' # success
724 yield '0\n' # success
720 repo.ui.debug('%d files, %d bytes to transfer\n' %
725 repo.ui.debug('%d files, %d bytes to transfer\n' %
721 (len(entries), total_bytes))
726 (len(entries), total_bytes))
722 yield '%d %d\n' % (len(entries), total_bytes)
727 yield '%d %d\n' % (len(entries), total_bytes)
723
728
724 sopener = repo.sopener
729 sopener = repo.sopener
725 oldaudit = sopener.mustaudit
730 oldaudit = sopener.mustaudit
726 debugflag = repo.ui.debugflag
731 debugflag = repo.ui.debugflag
727 sopener.mustaudit = False
732 sopener.mustaudit = False
728
733
729 try:
734 try:
730 for name, size in entries:
735 for name, size in entries:
731 if debugflag:
736 if debugflag:
732 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
737 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
733 # partially encode name over the wire for backwards compat
738 # partially encode name over the wire for backwards compat
734 yield '%s\0%d\n' % (store.encodedir(name), size)
739 yield '%s\0%d\n' % (store.encodedir(name), size)
735 if size <= 65536:
740 if size <= 65536:
736 fp = sopener(name)
741 fp = sopener(name)
737 try:
742 try:
738 data = fp.read(size)
743 data = fp.read(size)
739 finally:
744 finally:
740 fp.close()
745 fp.close()
741 yield data
746 yield data
742 else:
747 else:
743 for chunk in util.filechunkiter(sopener(name), limit=size):
748 for chunk in util.filechunkiter(sopener(name), limit=size):
744 yield chunk
749 yield chunk
745 # replace with "finally:" when support for python 2.4 has been dropped
750 # replace with "finally:" when support for python 2.4 has been dropped
746 except Exception:
751 except Exception:
747 sopener.mustaudit = oldaudit
752 sopener.mustaudit = oldaudit
748 raise
753 raise
749 sopener.mustaudit = oldaudit
754 sopener.mustaudit = oldaudit
750
755
751 return streamres(streamer(repo, entries, total_bytes))
756 return streamres(streamer(repo, entries, total_bytes))
752
757
753 @wireprotocommand('unbundle', 'heads')
758 @wireprotocommand('unbundle', 'heads')
754 def unbundle(repo, proto, heads):
759 def unbundle(repo, proto, heads):
755 their_heads = decodelist(heads)
760 their_heads = decodelist(heads)
756
761
757 try:
762 try:
758 proto.redirect()
763 proto.redirect()
759
764
760 exchange.check_heads(repo, their_heads, 'preparing changes')
765 exchange.check_heads(repo, their_heads, 'preparing changes')
761
766
762 # write bundle data to temporary file because it can be big
767 # write bundle data to temporary file because it can be big
763 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
768 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
764 fp = os.fdopen(fd, 'wb+')
769 fp = os.fdopen(fd, 'wb+')
765 r = 0
770 r = 0
766 try:
771 try:
767 proto.getfile(fp)
772 proto.getfile(fp)
768 fp.seek(0)
773 fp.seek(0)
769 gen = exchange.readbundle(repo.ui, fp, None)
774 gen = exchange.readbundle(repo.ui, fp, None)
770 r = exchange.unbundle(repo, gen, their_heads, 'serve',
775 r = exchange.unbundle(repo, gen, their_heads, 'serve',
771 proto._client())
776 proto._client())
772 return pushres(r)
777 return pushres(r)
773
778
774 finally:
779 finally:
775 fp.close()
780 fp.close()
776 os.unlink(tempname)
781 os.unlink(tempname)
777 except util.Abort, inst:
782 except util.Abort, inst:
778 # The old code we moved used sys.stderr directly.
783 # The old code we moved used sys.stderr directly.
779 # We did not change it to minimise code change.
784 # We did not change it to minimise code change.
780 # This need to be moved to something proper.
785 # This need to be moved to something proper.
781 # Feel free to do it.
786 # Feel free to do it.
782 sys.stderr.write("abort: %s\n" % inst)
787 sys.stderr.write("abort: %s\n" % inst)
783 return pushres(0)
788 return pushres(0)
784 except exchange.PushRaced, exc:
789 except exchange.PushRaced, exc:
785 return pusherr(str(exc))
790 return pusherr(str(exc))
@@ -1,684 +1,714 b''
1
1
2 Create an extension to test bundle2 API
2 Create an extension to test bundle2 API
3
3
4 $ cat > bundle2.py << EOF
4 $ cat > bundle2.py << EOF
5 > """A small extension to test bundle2 implementation
5 > """A small extension to test bundle2 implementation
6 >
6 >
7 > Current bundle2 implementation is far too limited to be used in any core
7 > Current bundle2 implementation is far too limited to be used in any core
8 > code. We still need to be able to test it while it grow up.
8 > code. We still need to be able to test it while it grow up.
9 > """
9 > """
10 >
10 >
11 > import sys
11 > import sys
12 > from mercurial import cmdutil
12 > from mercurial import cmdutil
13 > from mercurial import util
13 > from mercurial import util
14 > from mercurial import bundle2
14 > from mercurial import bundle2
15 > from mercurial import scmutil
15 > from mercurial import scmutil
16 > from mercurial import discovery
16 > from mercurial import discovery
17 > from mercurial import changegroup
17 > from mercurial import changegroup
18 > cmdtable = {}
18 > cmdtable = {}
19 > command = cmdutil.command(cmdtable)
19 > command = cmdutil.command(cmdtable)
20 >
20 >
21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
25 >
25 >
26 > @bundle2.parthandler('test:song')
26 > @bundle2.parthandler('test:song')
27 > def songhandler(op, part):
27 > def songhandler(op, part):
28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
29 > op.ui.write('The choir starts singing:\n')
29 > op.ui.write('The choir starts singing:\n')
30 > verses = 0
30 > verses = 0
31 > for line in part.read().split('\n'):
31 > for line in part.read().split('\n'):
32 > op.ui.write(' %s\n' % line)
32 > op.ui.write(' %s\n' % line)
33 > verses += 1
33 > verses += 1
34 > op.records.add('song', {'verses': verses})
34 > op.records.add('song', {'verses': verses})
35 >
35 >
36 > @bundle2.parthandler('test:ping')
36 > @bundle2.parthandler('test:ping')
37 > def pinghandler(op, part):
37 > def pinghandler(op, part):
38 > op.ui.write('received ping request (id %i)\n' % part.id)
38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 > if op.reply is not None:
39 > if op.reply is not None:
40 > rpart = bundle2.bundlepart('test:pong',
40 > rpart = bundle2.bundlepart('test:pong',
41 > [('in-reply-to', str(part.id))])
41 > [('in-reply-to', str(part.id))])
42 > op.reply.addpart(rpart)
42 > op.reply.addpart(rpart)
43 >
43 >
44 > @command('bundle2',
44 > @command('bundle2',
45 > [('', 'param', [], 'stream level parameter'),
45 > [('', 'param', [], 'stream level parameter'),
46 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
46 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
47 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
47 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
48 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
48 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
49 > '[OUTPUTFILE]')
49 > '[OUTPUTFILE]')
50 > def cmdbundle2(ui, repo, path=None, **opts):
50 > def cmdbundle2(ui, repo, path=None, **opts):
51 > """write a bundle2 container on standard ouput"""
51 > """write a bundle2 container on standard ouput"""
52 > bundler = bundle2.bundle20(ui)
52 > bundler = bundle2.bundle20(ui)
53 > for p in opts['param']:
53 > for p in opts['param']:
54 > p = p.split('=', 1)
54 > p = p.split('=', 1)
55 > try:
55 > try:
56 > bundler.addparam(*p)
56 > bundler.addparam(*p)
57 > except ValueError, exc:
57 > except ValueError, exc:
58 > raise util.Abort('%s' % exc)
58 > raise util.Abort('%s' % exc)
59 >
59 >
60 > revs = opts['rev']
60 > revs = opts['rev']
61 > if 'rev' in opts:
61 > if 'rev' in opts:
62 > revs = scmutil.revrange(repo, opts['rev'])
62 > revs = scmutil.revrange(repo, opts['rev'])
63 > if revs:
63 > if revs:
64 > # very crude version of a changegroup part creation
64 > # very crude version of a changegroup part creation
65 > bundled = repo.revs('%ld::%ld', revs, revs)
65 > bundled = repo.revs('%ld::%ld', revs, revs)
66 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
66 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
67 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
67 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
68 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
68 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
69 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
69 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
70 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
70 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
71 > bundler.addpart(part)
71 > bundler.addpart(part)
72 >
72 >
73 > if opts['parts']:
73 > if opts['parts']:
74 > part = bundle2.bundlepart('test:empty')
74 > part = bundle2.bundlepart('test:empty')
75 > bundler.addpart(part)
75 > bundler.addpart(part)
76 > # add a second one to make sure we handle multiple parts
76 > # add a second one to make sure we handle multiple parts
77 > part = bundle2.bundlepart('test:empty')
77 > part = bundle2.bundlepart('test:empty')
78 > bundler.addpart(part)
78 > bundler.addpart(part)
79 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
79 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
80 > bundler.addpart(part)
80 > bundler.addpart(part)
81 > part = bundle2.bundlepart('test:math',
81 > part = bundle2.bundlepart('test:math',
82 > [('pi', '3.14'), ('e', '2.72')],
82 > [('pi', '3.14'), ('e', '2.72')],
83 > [('cooking', 'raw')],
83 > [('cooking', 'raw')],
84 > '42')
84 > '42')
85 > bundler.addpart(part)
85 > bundler.addpart(part)
86 > if opts['unknown']:
86 > if opts['unknown']:
87 > part = bundle2.bundlepart('test:UNKNOWN',
87 > part = bundle2.bundlepart('test:UNKNOWN',
88 > data='some random content')
88 > data='some random content')
89 > bundler.addpart(part)
89 > bundler.addpart(part)
90 > if opts['parts']:
90 > if opts['parts']:
91 > part = bundle2.bundlepart('test:ping')
91 > part = bundle2.bundlepart('test:ping')
92 > bundler.addpart(part)
92 > bundler.addpart(part)
93 >
93 >
94 > if path is None:
94 > if path is None:
95 > file = sys.stdout
95 > file = sys.stdout
96 > else:
96 > else:
97 > file = open(path, 'w')
97 > file = open(path, 'w')
98 >
98 >
99 > for chunk in bundler.getchunks():
99 > for chunk in bundler.getchunks():
100 > file.write(chunk)
100 > file.write(chunk)
101 >
101 >
102 > @command('unbundle2', [], '')
102 > @command('unbundle2', [], '')
103 > def cmdunbundle2(ui, repo, replypath=None):
103 > def cmdunbundle2(ui, repo, replypath=None):
104 > """process a bundle2 stream from stdin on the current repo"""
104 > """process a bundle2 stream from stdin on the current repo"""
105 > try:
105 > try:
106 > tr = None
106 > tr = None
107 > lock = repo.lock()
107 > lock = repo.lock()
108 > tr = repo.transaction('processbundle')
108 > tr = repo.transaction('processbundle')
109 > try:
109 > try:
110 > unbundler = bundle2.unbundle20(ui, sys.stdin)
110 > unbundler = bundle2.unbundle20(ui, sys.stdin)
111 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
111 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
112 > tr.close()
112 > tr.close()
113 > except KeyError, exc:
113 > except KeyError, exc:
114 > raise util.Abort('missing support for %s' % exc)
114 > raise util.Abort('missing support for %s' % exc)
115 > finally:
115 > finally:
116 > if tr is not None:
116 > if tr is not None:
117 > tr.release()
117 > tr.release()
118 > lock.release()
118 > lock.release()
119 > remains = sys.stdin.read()
119 > remains = sys.stdin.read()
120 > ui.write('%i unread bytes\n' % len(remains))
120 > ui.write('%i unread bytes\n' % len(remains))
121 > if op.records['song']:
121 > if op.records['song']:
122 > totalverses = sum(r['verses'] for r in op.records['song'])
122 > totalverses = sum(r['verses'] for r in op.records['song'])
123 > ui.write('%i total verses sung\n' % totalverses)
123 > ui.write('%i total verses sung\n' % totalverses)
124 > for rec in op.records['changegroup']:
124 > for rec in op.records['changegroup']:
125 > ui.write('addchangegroup return: %i\n' % rec['return'])
125 > ui.write('addchangegroup return: %i\n' % rec['return'])
126 > if op.reply is not None and replypath is not None:
126 > if op.reply is not None and replypath is not None:
127 > file = open(replypath, 'w')
127 > file = open(replypath, 'w')
128 > for chunk in op.reply.getchunks():
128 > for chunk in op.reply.getchunks():
129 > file.write(chunk)
129 > file.write(chunk)
130 >
130 >
131 > @command('statbundle2', [], '')
131 > @command('statbundle2', [], '')
132 > def cmdstatbundle2(ui, repo):
132 > def cmdstatbundle2(ui, repo):
133 > """print statistic on the bundle2 container read from stdin"""
133 > """print statistic on the bundle2 container read from stdin"""
134 > unbundler = bundle2.unbundle20(ui, sys.stdin)
134 > unbundler = bundle2.unbundle20(ui, sys.stdin)
135 > try:
135 > try:
136 > params = unbundler.params
136 > params = unbundler.params
137 > except KeyError, exc:
137 > except KeyError, exc:
138 > raise util.Abort('unknown parameters: %s' % exc)
138 > raise util.Abort('unknown parameters: %s' % exc)
139 > ui.write('options count: %i\n' % len(params))
139 > ui.write('options count: %i\n' % len(params))
140 > for key in sorted(params):
140 > for key in sorted(params):
141 > ui.write('- %s\n' % key)
141 > ui.write('- %s\n' % key)
142 > value = params[key]
142 > value = params[key]
143 > if value is not None:
143 > if value is not None:
144 > ui.write(' %s\n' % value)
144 > ui.write(' %s\n' % value)
145 > count = 0
145 > count = 0
146 > for p in unbundler:
146 > for p in unbundler:
147 > count += 1
147 > count += 1
148 > ui.write(' :%s:\n' % p.type)
148 > ui.write(' :%s:\n' % p.type)
149 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
149 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
150 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
150 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
151 > ui.write(' payload: %i bytes\n' % len(p.read()))
151 > ui.write(' payload: %i bytes\n' % len(p.read()))
152 > ui.write('parts count: %i\n' % count)
152 > ui.write('parts count: %i\n' % count)
153 > EOF
153 > EOF
154 $ cat >> $HGRCPATH << EOF
154 $ cat >> $HGRCPATH << EOF
155 > [extensions]
155 > [extensions]
156 > bundle2=$TESTTMP/bundle2.py
156 > bundle2=$TESTTMP/bundle2.py
157 > [server]
157 > [server]
158 > bundle2=True
158 > bundle2=True
159 > [ui]
160 > ssh=python "$TESTDIR/dummyssh"
159 > EOF
161 > EOF
160
162
161 The extension requires a repo (currently unused)
163 The extension requires a repo (currently unused)
162
164
163 $ hg init main
165 $ hg init main
164 $ cd main
166 $ cd main
165 $ touch a
167 $ touch a
166 $ hg add a
168 $ hg add a
167 $ hg commit -m 'a'
169 $ hg commit -m 'a'
168
170
169
171
170 Empty bundle
172 Empty bundle
171 =================
173 =================
172
174
173 - no option
175 - no option
174 - no parts
176 - no parts
175
177
176 Test bundling
178 Test bundling
177
179
178 $ hg bundle2
180 $ hg bundle2
179 HG20\x00\x00\x00\x00 (no-eol) (esc)
181 HG20\x00\x00\x00\x00 (no-eol) (esc)
180
182
181 Test unbundling
183 Test unbundling
182
184
183 $ hg bundle2 | hg statbundle2
185 $ hg bundle2 | hg statbundle2
184 options count: 0
186 options count: 0
185 parts count: 0
187 parts count: 0
186
188
187 Test old style bundle are detected and refused
189 Test old style bundle are detected and refused
188
190
189 $ hg bundle --all ../bundle.hg
191 $ hg bundle --all ../bundle.hg
190 1 changesets found
192 1 changesets found
191 $ hg statbundle2 < ../bundle.hg
193 $ hg statbundle2 < ../bundle.hg
192 abort: unknown bundle version 10
194 abort: unknown bundle version 10
193 [255]
195 [255]
194
196
195 Test parameters
197 Test parameters
196 =================
198 =================
197
199
198 - some options
200 - some options
199 - no parts
201 - no parts
200
202
201 advisory parameters, no value
203 advisory parameters, no value
202 -------------------------------
204 -------------------------------
203
205
204 Simplest possible parameters form
206 Simplest possible parameters form
205
207
206 Test generation simple option
208 Test generation simple option
207
209
208 $ hg bundle2 --param 'caution'
210 $ hg bundle2 --param 'caution'
209 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
211 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
210
212
211 Test unbundling
213 Test unbundling
212
214
213 $ hg bundle2 --param 'caution' | hg statbundle2
215 $ hg bundle2 --param 'caution' | hg statbundle2
214 options count: 1
216 options count: 1
215 - caution
217 - caution
216 parts count: 0
218 parts count: 0
217
219
218 Test generation multiple option
220 Test generation multiple option
219
221
220 $ hg bundle2 --param 'caution' --param 'meal'
222 $ hg bundle2 --param 'caution' --param 'meal'
221 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
223 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
222
224
223 Test unbundling
225 Test unbundling
224
226
225 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
227 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
226 options count: 2
228 options count: 2
227 - caution
229 - caution
228 - meal
230 - meal
229 parts count: 0
231 parts count: 0
230
232
231 advisory parameters, with value
233 advisory parameters, with value
232 -------------------------------
234 -------------------------------
233
235
234 Test generation
236 Test generation
235
237
236 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
238 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
237 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
239 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
238
240
239 Test unbundling
241 Test unbundling
240
242
241 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
243 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
242 options count: 3
244 options count: 3
243 - caution
245 - caution
244 - elephants
246 - elephants
245 - meal
247 - meal
246 vegan
248 vegan
247 parts count: 0
249 parts count: 0
248
250
249 parameter with special char in value
251 parameter with special char in value
250 ---------------------------------------------------
252 ---------------------------------------------------
251
253
252 Test generation
254 Test generation
253
255
254 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
256 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
255 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
257 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
256
258
257 Test unbundling
259 Test unbundling
258
260
259 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
261 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
260 options count: 2
262 options count: 2
261 - e|! 7/
263 - e|! 7/
262 babar%#==tutu
264 babar%#==tutu
263 - simple
265 - simple
264 parts count: 0
266 parts count: 0
265
267
266 Test unknown mandatory option
268 Test unknown mandatory option
267 ---------------------------------------------------
269 ---------------------------------------------------
268
270
269 $ hg bundle2 --param 'Gravity' | hg statbundle2
271 $ hg bundle2 --param 'Gravity' | hg statbundle2
270 abort: unknown parameters: 'Gravity'
272 abort: unknown parameters: 'Gravity'
271 [255]
273 [255]
272
274
273 Test debug output
275 Test debug output
274 ---------------------------------------------------
276 ---------------------------------------------------
275
277
276 bundling debug
278 bundling debug
277
279
278 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
280 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
279 start emission of HG20 stream
281 start emission of HG20 stream
280 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
282 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
281 start of parts
283 start of parts
282 end of bundle
284 end of bundle
283
285
284 file content is ok
286 file content is ok
285
287
286 $ cat ../out.hg2
288 $ cat ../out.hg2
287 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
289 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
288
290
289 unbundling debug
291 unbundling debug
290
292
291 $ hg statbundle2 --debug < ../out.hg2
293 $ hg statbundle2 --debug < ../out.hg2
292 start processing of HG20 stream
294 start processing of HG20 stream
293 reading bundle2 stream parameters
295 reading bundle2 stream parameters
294 ignoring unknown parameter 'e|! 7/'
296 ignoring unknown parameter 'e|! 7/'
295 ignoring unknown parameter 'simple'
297 ignoring unknown parameter 'simple'
296 options count: 2
298 options count: 2
297 - e|! 7/
299 - e|! 7/
298 babar%#==tutu
300 babar%#==tutu
299 - simple
301 - simple
300 start extraction of bundle2 parts
302 start extraction of bundle2 parts
301 part header size: 0
303 part header size: 0
302 end of bundle2 stream
304 end of bundle2 stream
303 parts count: 0
305 parts count: 0
304
306
305
307
306 Test buggy input
308 Test buggy input
307 ---------------------------------------------------
309 ---------------------------------------------------
308
310
309 empty parameter name
311 empty parameter name
310
312
311 $ hg bundle2 --param '' --quiet
313 $ hg bundle2 --param '' --quiet
312 abort: empty parameter name
314 abort: empty parameter name
313 [255]
315 [255]
314
316
315 bad parameter name
317 bad parameter name
316
318
317 $ hg bundle2 --param 42babar
319 $ hg bundle2 --param 42babar
318 abort: non letter first character: '42babar'
320 abort: non letter first character: '42babar'
319 [255]
321 [255]
320
322
321
323
322 Test part
324 Test part
323 =================
325 =================
324
326
325 $ hg bundle2 --parts ../parts.hg2 --debug
327 $ hg bundle2 --parts ../parts.hg2 --debug
326 start emission of HG20 stream
328 start emission of HG20 stream
327 bundle parameter:
329 bundle parameter:
328 start of parts
330 start of parts
329 bundle part: "test:empty"
331 bundle part: "test:empty"
330 bundle part: "test:empty"
332 bundle part: "test:empty"
331 bundle part: "test:song"
333 bundle part: "test:song"
332 bundle part: "test:math"
334 bundle part: "test:math"
333 bundle part: "test:ping"
335 bundle part: "test:ping"
334 end of bundle
336 end of bundle
335
337
336 $ cat ../parts.hg2
338 $ cat ../parts.hg2
337 HG20\x00\x00\x00\x11 (esc)
339 HG20\x00\x00\x00\x11 (esc)
338 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
340 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
339 test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
341 test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
340 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
342 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
341 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
343 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
342
344
343
345
344 $ hg statbundle2 < ../parts.hg2
346 $ hg statbundle2 < ../parts.hg2
345 options count: 0
347 options count: 0
346 :test:empty:
348 :test:empty:
347 mandatory: 0
349 mandatory: 0
348 advisory: 0
350 advisory: 0
349 payload: 0 bytes
351 payload: 0 bytes
350 :test:empty:
352 :test:empty:
351 mandatory: 0
353 mandatory: 0
352 advisory: 0
354 advisory: 0
353 payload: 0 bytes
355 payload: 0 bytes
354 :test:song:
356 :test:song:
355 mandatory: 0
357 mandatory: 0
356 advisory: 0
358 advisory: 0
357 payload: 178 bytes
359 payload: 178 bytes
358 :test:math:
360 :test:math:
359 mandatory: 2
361 mandatory: 2
360 advisory: 1
362 advisory: 1
361 payload: 2 bytes
363 payload: 2 bytes
362 :test:ping:
364 :test:ping:
363 mandatory: 0
365 mandatory: 0
364 advisory: 0
366 advisory: 0
365 payload: 0 bytes
367 payload: 0 bytes
366 parts count: 5
368 parts count: 5
367
369
368 $ hg statbundle2 --debug < ../parts.hg2
370 $ hg statbundle2 --debug < ../parts.hg2
369 start processing of HG20 stream
371 start processing of HG20 stream
370 reading bundle2 stream parameters
372 reading bundle2 stream parameters
371 options count: 0
373 options count: 0
372 start extraction of bundle2 parts
374 start extraction of bundle2 parts
373 part header size: 17
375 part header size: 17
374 part type: "test:empty"
376 part type: "test:empty"
375 part id: "0"
377 part id: "0"
376 part parameters: 0
378 part parameters: 0
377 :test:empty:
379 :test:empty:
378 mandatory: 0
380 mandatory: 0
379 advisory: 0
381 advisory: 0
380 payload chunk size: 0
382 payload chunk size: 0
381 payload: 0 bytes
383 payload: 0 bytes
382 part header size: 17
384 part header size: 17
383 part type: "test:empty"
385 part type: "test:empty"
384 part id: "1"
386 part id: "1"
385 part parameters: 0
387 part parameters: 0
386 :test:empty:
388 :test:empty:
387 mandatory: 0
389 mandatory: 0
388 advisory: 0
390 advisory: 0
389 payload chunk size: 0
391 payload chunk size: 0
390 payload: 0 bytes
392 payload: 0 bytes
391 part header size: 16
393 part header size: 16
392 part type: "test:song"
394 part type: "test:song"
393 part id: "2"
395 part id: "2"
394 part parameters: 0
396 part parameters: 0
395 :test:song:
397 :test:song:
396 mandatory: 0
398 mandatory: 0
397 advisory: 0
399 advisory: 0
398 payload chunk size: 178
400 payload chunk size: 178
399 payload chunk size: 0
401 payload chunk size: 0
400 payload: 178 bytes
402 payload: 178 bytes
401 part header size: 43
403 part header size: 43
402 part type: "test:math"
404 part type: "test:math"
403 part id: "3"
405 part id: "3"
404 part parameters: 3
406 part parameters: 3
405 :test:math:
407 :test:math:
406 mandatory: 2
408 mandatory: 2
407 advisory: 1
409 advisory: 1
408 payload chunk size: 2
410 payload chunk size: 2
409 payload chunk size: 0
411 payload chunk size: 0
410 payload: 2 bytes
412 payload: 2 bytes
411 part header size: 16
413 part header size: 16
412 part type: "test:ping"
414 part type: "test:ping"
413 part id: "4"
415 part id: "4"
414 part parameters: 0
416 part parameters: 0
415 :test:ping:
417 :test:ping:
416 mandatory: 0
418 mandatory: 0
417 advisory: 0
419 advisory: 0
418 payload chunk size: 0
420 payload chunk size: 0
419 payload: 0 bytes
421 payload: 0 bytes
420 part header size: 0
422 part header size: 0
421 end of bundle2 stream
423 end of bundle2 stream
422 parts count: 5
424 parts count: 5
423
425
424 Test actual unbundling of test part
426 Test actual unbundling of test part
425 =======================================
427 =======================================
426
428
427 Process the bundle
429 Process the bundle
428
430
429 $ hg unbundle2 --debug < ../parts.hg2
431 $ hg unbundle2 --debug < ../parts.hg2
430 start processing of HG20 stream
432 start processing of HG20 stream
431 reading bundle2 stream parameters
433 reading bundle2 stream parameters
432 start extraction of bundle2 parts
434 start extraction of bundle2 parts
433 part header size: 17
435 part header size: 17
434 part type: "test:empty"
436 part type: "test:empty"
435 part id: "0"
437 part id: "0"
436 part parameters: 0
438 part parameters: 0
437 ignoring unknown advisory part 'test:empty'
439 ignoring unknown advisory part 'test:empty'
438 payload chunk size: 0
440 payload chunk size: 0
439 part header size: 17
441 part header size: 17
440 part type: "test:empty"
442 part type: "test:empty"
441 part id: "1"
443 part id: "1"
442 part parameters: 0
444 part parameters: 0
443 ignoring unknown advisory part 'test:empty'
445 ignoring unknown advisory part 'test:empty'
444 payload chunk size: 0
446 payload chunk size: 0
445 part header size: 16
447 part header size: 16
446 part type: "test:song"
448 part type: "test:song"
447 part id: "2"
449 part id: "2"
448 part parameters: 0
450 part parameters: 0
449 found a handler for part 'test:song'
451 found a handler for part 'test:song'
450 The choir starts singing:
452 The choir starts singing:
451 payload chunk size: 178
453 payload chunk size: 178
452 payload chunk size: 0
454 payload chunk size: 0
453 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
455 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
454 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
456 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
455 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
457 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
456 part header size: 43
458 part header size: 43
457 part type: "test:math"
459 part type: "test:math"
458 part id: "3"
460 part id: "3"
459 part parameters: 3
461 part parameters: 3
460 ignoring unknown advisory part 'test:math'
462 ignoring unknown advisory part 'test:math'
461 payload chunk size: 2
463 payload chunk size: 2
462 payload chunk size: 0
464 payload chunk size: 0
463 part header size: 16
465 part header size: 16
464 part type: "test:ping"
466 part type: "test:ping"
465 part id: "4"
467 part id: "4"
466 part parameters: 0
468 part parameters: 0
467 found a handler for part 'test:ping'
469 found a handler for part 'test:ping'
468 received ping request (id 4)
470 received ping request (id 4)
469 payload chunk size: 0
471 payload chunk size: 0
470 part header size: 0
472 part header size: 0
471 end of bundle2 stream
473 end of bundle2 stream
472 0 unread bytes
474 0 unread bytes
473 3 total verses sung
475 3 total verses sung
474
476
475 Unbundle with an unknown mandatory part
477 Unbundle with an unknown mandatory part
476 (should abort)
478 (should abort)
477
479
478 $ hg bundle2 --parts --unknown ../unknown.hg2
480 $ hg bundle2 --parts --unknown ../unknown.hg2
479
481
480 $ hg unbundle2 < ../unknown.hg2
482 $ hg unbundle2 < ../unknown.hg2
481 The choir starts singing:
483 The choir starts singing:
482 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
484 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
483 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
485 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
484 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
486 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
485 0 unread bytes
487 0 unread bytes
486 abort: missing support for 'test:unknown'
488 abort: missing support for 'test:unknown'
487 [255]
489 [255]
488
490
489 unbundle with a reply
491 unbundle with a reply
490
492
491 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
493 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
492 The choir starts singing:
494 The choir starts singing:
493 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
495 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
494 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
496 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
495 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
497 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
496 received ping request (id 4)
498 received ping request (id 4)
497 0 unread bytes
499 0 unread bytes
498 3 total verses sung
500 3 total verses sung
499
501
500 The reply is a bundle
502 The reply is a bundle
501
503
502 $ cat ../reply.hg2
504 $ cat ../reply.hg2
503 HG20\x00\x00\x00\x1e test:pong\x00\x00\x00\x00\x01\x00\x0b\x01in-reply-to4\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
505 HG20\x00\x00\x00\x1e test:pong\x00\x00\x00\x00\x01\x00\x0b\x01in-reply-to4\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
504
506
505 The reply is valid
507 The reply is valid
506
508
507 $ hg statbundle2 < ../reply.hg2
509 $ hg statbundle2 < ../reply.hg2
508 options count: 0
510 options count: 0
509 :test:pong:
511 :test:pong:
510 mandatory: 1
512 mandatory: 1
511 advisory: 0
513 advisory: 0
512 payload: 0 bytes
514 payload: 0 bytes
513 parts count: 1
515 parts count: 1
514
516
515 Support for changegroup
517 Support for changegroup
516 ===================================
518 ===================================
517
519
518 $ hg unbundle $TESTDIR/bundles/rebase.hg
520 $ hg unbundle $TESTDIR/bundles/rebase.hg
519 adding changesets
521 adding changesets
520 adding manifests
522 adding manifests
521 adding file changes
523 adding file changes
522 added 8 changesets with 7 changes to 7 files (+3 heads)
524 added 8 changesets with 7 changes to 7 files (+3 heads)
523 (run 'hg heads' to see heads, 'hg merge' to merge)
525 (run 'hg heads' to see heads, 'hg merge' to merge)
524
526
525 $ hg log -G
527 $ hg log -G
526 o changeset: 8:02de42196ebe
528 o changeset: 8:02de42196ebe
527 | tag: tip
529 | tag: tip
528 | parent: 6:24b6387c8c8c
530 | parent: 6:24b6387c8c8c
529 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
531 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
530 | date: Sat Apr 30 15:24:48 2011 +0200
532 | date: Sat Apr 30 15:24:48 2011 +0200
531 | summary: H
533 | summary: H
532 |
534 |
533 | o changeset: 7:eea13746799a
535 | o changeset: 7:eea13746799a
534 |/| parent: 6:24b6387c8c8c
536 |/| parent: 6:24b6387c8c8c
535 | | parent: 5:9520eea781bc
537 | | parent: 5:9520eea781bc
536 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
538 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
537 | | date: Sat Apr 30 15:24:48 2011 +0200
539 | | date: Sat Apr 30 15:24:48 2011 +0200
538 | | summary: G
540 | | summary: G
539 | |
541 | |
540 o | changeset: 6:24b6387c8c8c
542 o | changeset: 6:24b6387c8c8c
541 | | parent: 1:cd010b8cd998
543 | | parent: 1:cd010b8cd998
542 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
544 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
543 | | date: Sat Apr 30 15:24:48 2011 +0200
545 | | date: Sat Apr 30 15:24:48 2011 +0200
544 | | summary: F
546 | | summary: F
545 | |
547 | |
546 | o changeset: 5:9520eea781bc
548 | o changeset: 5:9520eea781bc
547 |/ parent: 1:cd010b8cd998
549 |/ parent: 1:cd010b8cd998
548 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
550 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
549 | date: Sat Apr 30 15:24:48 2011 +0200
551 | date: Sat Apr 30 15:24:48 2011 +0200
550 | summary: E
552 | summary: E
551 |
553 |
552 | o changeset: 4:32af7686d403
554 | o changeset: 4:32af7686d403
553 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
555 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
554 | | date: Sat Apr 30 15:24:48 2011 +0200
556 | | date: Sat Apr 30 15:24:48 2011 +0200
555 | | summary: D
557 | | summary: D
556 | |
558 | |
557 | o changeset: 3:5fddd98957c8
559 | o changeset: 3:5fddd98957c8
558 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
560 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
559 | | date: Sat Apr 30 15:24:48 2011 +0200
561 | | date: Sat Apr 30 15:24:48 2011 +0200
560 | | summary: C
562 | | summary: C
561 | |
563 | |
562 | o changeset: 2:42ccdea3bb16
564 | o changeset: 2:42ccdea3bb16
563 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
565 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
564 | date: Sat Apr 30 15:24:48 2011 +0200
566 | date: Sat Apr 30 15:24:48 2011 +0200
565 | summary: B
567 | summary: B
566 |
568 |
567 o changeset: 1:cd010b8cd998
569 o changeset: 1:cd010b8cd998
568 parent: -1:000000000000
570 parent: -1:000000000000
569 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
571 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
570 date: Sat Apr 30 15:24:48 2011 +0200
572 date: Sat Apr 30 15:24:48 2011 +0200
571 summary: A
573 summary: A
572
574
573 @ changeset: 0:3903775176ed
575 @ changeset: 0:3903775176ed
574 user: test
576 user: test
575 date: Thu Jan 01 00:00:00 1970 +0000
577 date: Thu Jan 01 00:00:00 1970 +0000
576 summary: a
578 summary: a
577
579
578
580
579 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
581 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
580 4 changesets found
582 4 changesets found
581 list of changesets:
583 list of changesets:
582 32af7686d403cf45b5d95f2d70cebea587ac806a
584 32af7686d403cf45b5d95f2d70cebea587ac806a
583 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
585 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
584 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
586 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
585 02de42196ebee42ef284b6780a87cdc96e8eaab6
587 02de42196ebee42ef284b6780a87cdc96e8eaab6
586 start emission of HG20 stream
588 start emission of HG20 stream
587 bundle parameter:
589 bundle parameter:
588 start of parts
590 start of parts
589 bundle part: "changegroup"
591 bundle part: "changegroup"
590 bundling: 1/4 changesets (25.00%)
592 bundling: 1/4 changesets (25.00%)
591 bundling: 2/4 changesets (50.00%)
593 bundling: 2/4 changesets (50.00%)
592 bundling: 3/4 changesets (75.00%)
594 bundling: 3/4 changesets (75.00%)
593 bundling: 4/4 changesets (100.00%)
595 bundling: 4/4 changesets (100.00%)
594 bundling: 1/4 manifests (25.00%)
596 bundling: 1/4 manifests (25.00%)
595 bundling: 2/4 manifests (50.00%)
597 bundling: 2/4 manifests (50.00%)
596 bundling: 3/4 manifests (75.00%)
598 bundling: 3/4 manifests (75.00%)
597 bundling: 4/4 manifests (100.00%)
599 bundling: 4/4 manifests (100.00%)
598 bundling: D 1/3 files (33.33%)
600 bundling: D 1/3 files (33.33%)
599 bundling: E 2/3 files (66.67%)
601 bundling: E 2/3 files (66.67%)
600 bundling: H 3/3 files (100.00%)
602 bundling: H 3/3 files (100.00%)
601 end of bundle
603 end of bundle
602
604
603 $ cat ../rev.hg2
605 $ cat ../rev.hg2
604 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
606 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
605 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
607 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
606 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
608 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
607 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
609 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
608 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
610 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
609 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
611 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
610 \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
612 \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
611 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
613 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
612 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
614 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
613 \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
615 \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
614 \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
616 \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
615 \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
617 \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
616 \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
618 \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
617 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
619 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
618 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
620 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
619 \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02D (esc)
621 \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02D (esc)
620 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
622 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
621 l\r (no-eol) (esc)
623 l\r (no-eol) (esc)
622 \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
624 \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
623 \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
625 \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
624 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
626 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
625 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
627 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
626
628
627 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
629 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
628 adding changesets
630 adding changesets
629 adding manifests
631 adding manifests
630 adding file changes
632 adding file changes
631 added 0 changesets with 0 changes to 3 files
633 added 0 changesets with 0 changes to 3 files
632 0 unread bytes
634 0 unread bytes
633 addchangegroup return: 1
635 addchangegroup return: 1
634
636
635 $ cat ../rev-replay.hg2
637 $ cat ../rev-replay.hg2
636 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to0return1\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
638 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to0return1\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
637
639
638 Real world exchange
640 Real world exchange
639 =====================
641 =====================
640
642
641
643
642 clone --pull
644 clone --pull
643
645
644 $ cd ..
646 $ cd ..
645 $ hg clone main other --pull --rev 9520eea781bc
647 $ hg clone main other --pull --rev 9520eea781bc
646 adding changesets
648 adding changesets
647 adding manifests
649 adding manifests
648 adding file changes
650 adding file changes
649 added 2 changesets with 2 changes to 2 files
651 added 2 changesets with 2 changes to 2 files
650 updating to branch default
652 updating to branch default
651 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
653 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
652 $ hg -R other log -G
654 $ hg -R other log -G
653 @ changeset: 1:9520eea781bc
655 @ changeset: 1:9520eea781bc
654 | tag: tip
656 | tag: tip
655 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
657 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
656 | date: Sat Apr 30 15:24:48 2011 +0200
658 | date: Sat Apr 30 15:24:48 2011 +0200
657 | summary: E
659 | summary: E
658 |
660 |
659 o changeset: 0:cd010b8cd998
661 o changeset: 0:cd010b8cd998
660 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
662 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
661 date: Sat Apr 30 15:24:48 2011 +0200
663 date: Sat Apr 30 15:24:48 2011 +0200
662 summary: A
664 summary: A
663
665
664
666
665 pull
667 pull
666
668
667 $ hg -R other pull -r 24b6387c8c8c
669 $ hg -R other pull -r 24b6387c8c8c
668 pulling from $TESTTMP/main (glob)
670 pulling from $TESTTMP/main (glob)
669 searching for changes
671 searching for changes
670 adding changesets
672 adding changesets
671 adding manifests
673 adding manifests
672 adding file changes
674 adding file changes
673 added 1 changesets with 1 changes to 1 files (+1 heads)
675 added 1 changesets with 1 changes to 1 files (+1 heads)
674 (run 'hg heads' to see heads, 'hg merge' to merge)
676 (run 'hg heads' to see heads, 'hg merge' to merge)
675
677
676 push
678 push
677
679
678 $ hg -R main push other --rev eea13746799a
680 $ hg -R main push other --rev eea13746799a
679 pushing to other
681 pushing to other
680 searching for changes
682 searching for changes
681 adding changesets
683 adding changesets
682 adding manifests
684 adding manifests
683 adding file changes
685 adding file changes
684 added 1 changesets with 0 changes to 0 files (-1 heads)
686 added 1 changesets with 0 changes to 0 files (-1 heads)
687
688 pull over ssh
689
690 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
691 pulling from ssh://user@dummy/main
692 searching for changes
693 adding changesets
694 adding manifests
695 adding file changes
696 added 1 changesets with 1 changes to 1 files (+1 heads)
697 (run 'hg heads' to see heads, 'hg merge' to merge)
698
699 pull over http
700
701 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
702 $ cat main.pid >> $DAEMON_PIDS
703
704 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
705 pulling from http://localhost:$HGPORT/
706 searching for changes
707 adding changesets
708 adding manifests
709 adding file changes
710 added 1 changesets with 1 changes to 1 files (+1 heads)
711 (run 'hg heads .' to see heads, 'hg merge' to merge)
712 $ cat main-error.log
713
714
General Comments 0
You need to be logged in to leave comments. Login now