##// END OF EJS Templates
bundle2: support for push over the wire...
Pierre-Yves David -
r21075:438803e4 default
parent child Browse files
Show More
@@ -1,797 +1,811 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import urllib, tempfile, os, sys
8 import urllib, tempfile, os, sys
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import changegroup as changegroupmod, bundle2
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 if bundlecaps is not None and 'HG20' in bundlecaps:
338 if bundlecaps is not None and 'HG20' in bundlecaps:
339 return bundle2.unbundle20(self.ui, f)
339 return bundle2.unbundle20(self.ui, f)
340 else:
340 else:
341 return changegroupmod.unbundle10(f, 'UN')
341 return changegroupmod.unbundle10(f, 'UN')
342
342
343 def unbundle(self, cg, heads, source):
343 def unbundle(self, cg, heads, source):
344 '''Send cg (a readable file-like object representing the
344 '''Send cg (a readable file-like object representing the
345 changegroup to push, typically a chunkbuffer object) to the
345 changegroup to push, typically a chunkbuffer object) to the
346 remote server as a bundle. Return an integer indicating the
346 remote server as a bundle.
347 result of the push (see localrepository.addchangegroup()).'''
347
348 When pushing a bundle10 stream, return an integer indicating the
349 result of the push (see localrepository.addchangegroup()).
350
351 When pushing a bundle20 stream, return a bundle20 stream.'''
348
352
349 if heads != ['force'] and self.capable('unbundlehash'):
353 if heads != ['force'] and self.capable('unbundlehash'):
350 heads = encodelist(['hashed',
354 heads = encodelist(['hashed',
351 util.sha1(''.join(sorted(heads))).digest()])
355 util.sha1(''.join(sorted(heads))).digest()])
352 else:
356 else:
353 heads = encodelist(heads)
357 heads = encodelist(heads)
354
358
355 ret, output = self._callpush("unbundle", cg, heads=heads)
359 if util.safehasattr(cg, 'deltaheader'):
356 if ret == "":
360 # this a bundle10, do the old style call sequence
357 raise error.ResponseError(
361 ret, output = self._callpush("unbundle", cg, heads=heads)
358 _('push failed:'), output)
362 if ret == "":
359 try:
363 raise error.ResponseError(
360 ret = int(ret)
364 _('push failed:'), output)
361 except ValueError:
365 try:
362 raise error.ResponseError(
366 ret = int(ret)
363 _('push failed (unexpected response):'), ret)
367 except ValueError:
368 raise error.ResponseError(
369 _('push failed (unexpected response):'), ret)
364
370
365 for l in output.splitlines(True):
371 for l in output.splitlines(True):
366 self.ui.status(_('remote: '), l)
372 self.ui.status(_('remote: '), l)
373 else:
374 # bundle2 push. Send a stream, fetch a stream.
375 stream = self._calltwowaystream('unbundle', cg, heads=heads)
376 ret = bundle2.unbundle20(self.ui, stream)
367 return ret
377 return ret
368
378
369 def debugwireargs(self, one, two, three=None, four=None, five=None):
379 def debugwireargs(self, one, two, three=None, four=None, five=None):
370 # don't pass optional arguments left at their default value
380 # don't pass optional arguments left at their default value
371 opts = {}
381 opts = {}
372 if three is not None:
382 if three is not None:
373 opts['three'] = three
383 opts['three'] = three
374 if four is not None:
384 if four is not None:
375 opts['four'] = four
385 opts['four'] = four
376 return self._call('debugwireargs', one=one, two=two, **opts)
386 return self._call('debugwireargs', one=one, two=two, **opts)
377
387
378 def _call(self, cmd, **args):
388 def _call(self, cmd, **args):
379 """execute <cmd> on the server
389 """execute <cmd> on the server
380
390
381 The command is expected to return a simple string.
391 The command is expected to return a simple string.
382
392
383 returns the server reply as a string."""
393 returns the server reply as a string."""
384 raise NotImplementedError()
394 raise NotImplementedError()
385
395
386 def _callstream(self, cmd, **args):
396 def _callstream(self, cmd, **args):
387 """execute <cmd> on the server
397 """execute <cmd> on the server
388
398
389 The command is expected to return a stream.
399 The command is expected to return a stream.
390
400
391 returns the server reply as a file like object."""
401 returns the server reply as a file like object."""
392 raise NotImplementedError()
402 raise NotImplementedError()
393
403
394 def _callcompressable(self, cmd, **args):
404 def _callcompressable(self, cmd, **args):
395 """execute <cmd> on the server
405 """execute <cmd> on the server
396
406
397 The command is expected to return a stream.
407 The command is expected to return a stream.
398
408
399 The stream may have been compressed in some implementations. This
409 The stream may have been compressed in some implementations. This
400 function takes care of the decompression. This is the only difference
410 function takes care of the decompression. This is the only difference
401 with _callstream.
411 with _callstream.
402
412
403 returns the server reply as a file like object.
413 returns the server reply as a file like object.
404 """
414 """
405 raise NotImplementedError()
415 raise NotImplementedError()
406
416
407 def _callpush(self, cmd, fp, **args):
417 def _callpush(self, cmd, fp, **args):
408 """execute a <cmd> on server
418 """execute a <cmd> on server
409
419
410 The command is expected to be related to a push. Push has a special
420 The command is expected to be related to a push. Push has a special
411 return method.
421 return method.
412
422
413 returns the server reply as a (ret, output) tuple. ret is either
423 returns the server reply as a (ret, output) tuple. ret is either
414 empty (error) or a stringified int.
424 empty (error) or a stringified int.
415 """
425 """
416 raise NotImplementedError()
426 raise NotImplementedError()
417
427
418 def _calltwowaystream(self, cmd, fp, **args):
428 def _calltwowaystream(self, cmd, fp, **args):
419 """execute <cmd> on server
429 """execute <cmd> on server
420
430
421 The command will send a stream to the server and get a stream in reply.
431 The command will send a stream to the server and get a stream in reply.
422 """
432 """
423 raise NotImplementedError()
433 raise NotImplementedError()
424
434
425 def _abort(self, exception):
435 def _abort(self, exception):
426 """clearly abort the wire protocol connection and raise the exception
436 """clearly abort the wire protocol connection and raise the exception
427 """
437 """
428 raise NotImplementedError()
438 raise NotImplementedError()
429
439
430 # server side
440 # server side
431
441
432 # wire protocol command can either return a string or one of these classes.
442 # wire protocol command can either return a string or one of these classes.
433 class streamres(object):
443 class streamres(object):
434 """wireproto reply: binary stream
444 """wireproto reply: binary stream
435
445
436 The call was successful and the result is a stream.
446 The call was successful and the result is a stream.
437 Iterate on the `self.gen` attribute to retrieve chunks.
447 Iterate on the `self.gen` attribute to retrieve chunks.
438 """
448 """
439 def __init__(self, gen):
449 def __init__(self, gen):
440 self.gen = gen
450 self.gen = gen
441
451
442 class pushres(object):
452 class pushres(object):
443 """wireproto reply: success with simple integer return
453 """wireproto reply: success with simple integer return
444
454
445 The call was successful and returned an integer contained in `self.res`.
455 The call was successful and returned an integer contained in `self.res`.
446 """
456 """
447 def __init__(self, res):
457 def __init__(self, res):
448 self.res = res
458 self.res = res
449
459
450 class pusherr(object):
460 class pusherr(object):
451 """wireproto reply: failure
461 """wireproto reply: failure
452
462
453 The call failed. The `self.res` attribute contains the error message.
463 The call failed. The `self.res` attribute contains the error message.
454 """
464 """
455 def __init__(self, res):
465 def __init__(self, res):
456 self.res = res
466 self.res = res
457
467
458 class ooberror(object):
468 class ooberror(object):
459 """wireproto reply: failure of a batch of operation
469 """wireproto reply: failure of a batch of operation
460
470
461 Something failed during a batch call. The error message is stored in
471 Something failed during a batch call. The error message is stored in
462 `self.message`.
472 `self.message`.
463 """
473 """
464 def __init__(self, message):
474 def __init__(self, message):
465 self.message = message
475 self.message = message
466
476
467 def dispatch(repo, proto, command):
477 def dispatch(repo, proto, command):
468 repo = repo.filtered("served")
478 repo = repo.filtered("served")
469 func, spec = commands[command]
479 func, spec = commands[command]
470 args = proto.getargs(spec)
480 args = proto.getargs(spec)
471 return func(repo, proto, *args)
481 return func(repo, proto, *args)
472
482
473 def options(cmd, keys, others):
483 def options(cmd, keys, others):
474 opts = {}
484 opts = {}
475 for k in keys:
485 for k in keys:
476 if k in others:
486 if k in others:
477 opts[k] = others[k]
487 opts[k] = others[k]
478 del others[k]
488 del others[k]
479 if others:
489 if others:
480 sys.stderr.write("abort: %s got unexpected arguments %s\n"
490 sys.stderr.write("abort: %s got unexpected arguments %s\n"
481 % (cmd, ",".join(others)))
491 % (cmd, ",".join(others)))
482 return opts
492 return opts
483
493
484 # list of commands
494 # list of commands
485 commands = {}
495 commands = {}
486
496
487 def wireprotocommand(name, args=''):
497 def wireprotocommand(name, args=''):
488 """decorator for wire protocol command"""
498 """decorator for wire protocol command"""
489 def register(func):
499 def register(func):
490 commands[name] = (func, args)
500 commands[name] = (func, args)
491 return func
501 return func
492 return register
502 return register
493
503
494 @wireprotocommand('batch', 'cmds *')
504 @wireprotocommand('batch', 'cmds *')
495 def batch(repo, proto, cmds, others):
505 def batch(repo, proto, cmds, others):
496 repo = repo.filtered("served")
506 repo = repo.filtered("served")
497 res = []
507 res = []
498 for pair in cmds.split(';'):
508 for pair in cmds.split(';'):
499 op, args = pair.split(' ', 1)
509 op, args = pair.split(' ', 1)
500 vals = {}
510 vals = {}
501 for a in args.split(','):
511 for a in args.split(','):
502 if a:
512 if a:
503 n, v = a.split('=')
513 n, v = a.split('=')
504 vals[n] = unescapearg(v)
514 vals[n] = unescapearg(v)
505 func, spec = commands[op]
515 func, spec = commands[op]
506 if spec:
516 if spec:
507 keys = spec.split()
517 keys = spec.split()
508 data = {}
518 data = {}
509 for k in keys:
519 for k in keys:
510 if k == '*':
520 if k == '*':
511 star = {}
521 star = {}
512 for key in vals.keys():
522 for key in vals.keys():
513 if key not in keys:
523 if key not in keys:
514 star[key] = vals[key]
524 star[key] = vals[key]
515 data['*'] = star
525 data['*'] = star
516 else:
526 else:
517 data[k] = vals[k]
527 data[k] = vals[k]
518 result = func(repo, proto, *[data[k] for k in keys])
528 result = func(repo, proto, *[data[k] for k in keys])
519 else:
529 else:
520 result = func(repo, proto)
530 result = func(repo, proto)
521 if isinstance(result, ooberror):
531 if isinstance(result, ooberror):
522 return result
532 return result
523 res.append(escapearg(result))
533 res.append(escapearg(result))
524 return ';'.join(res)
534 return ';'.join(res)
525
535
526 @wireprotocommand('between', 'pairs')
536 @wireprotocommand('between', 'pairs')
527 def between(repo, proto, pairs):
537 def between(repo, proto, pairs):
528 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
538 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
529 r = []
539 r = []
530 for b in repo.between(pairs):
540 for b in repo.between(pairs):
531 r.append(encodelist(b) + "\n")
541 r.append(encodelist(b) + "\n")
532 return "".join(r)
542 return "".join(r)
533
543
534 @wireprotocommand('branchmap')
544 @wireprotocommand('branchmap')
535 def branchmap(repo, proto):
545 def branchmap(repo, proto):
536 branchmap = repo.branchmap()
546 branchmap = repo.branchmap()
537 heads = []
547 heads = []
538 for branch, nodes in branchmap.iteritems():
548 for branch, nodes in branchmap.iteritems():
539 branchname = urllib.quote(encoding.fromlocal(branch))
549 branchname = urllib.quote(encoding.fromlocal(branch))
540 branchnodes = encodelist(nodes)
550 branchnodes = encodelist(nodes)
541 heads.append('%s %s' % (branchname, branchnodes))
551 heads.append('%s %s' % (branchname, branchnodes))
542 return '\n'.join(heads)
552 return '\n'.join(heads)
543
553
544 @wireprotocommand('branches', 'nodes')
554 @wireprotocommand('branches', 'nodes')
545 def branches(repo, proto, nodes):
555 def branches(repo, proto, nodes):
546 nodes = decodelist(nodes)
556 nodes = decodelist(nodes)
547 r = []
557 r = []
548 for b in repo.branches(nodes):
558 for b in repo.branches(nodes):
549 r.append(encodelist(b) + "\n")
559 r.append(encodelist(b) + "\n")
550 return "".join(r)
560 return "".join(r)
551
561
552
562
553 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
563 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
554 'known', 'getbundle', 'unbundlehash', 'batch']
564 'known', 'getbundle', 'unbundlehash', 'batch']
555
565
556 def _capabilities(repo, proto):
566 def _capabilities(repo, proto):
557 """return a list of capabilities for a repo
567 """return a list of capabilities for a repo
558
568
559 This function exists to allow extensions to easily wrap capabilities
569 This function exists to allow extensions to easily wrap capabilities
560 computation
570 computation
561
571
562 - returns a lists: easy to alter
572 - returns a lists: easy to alter
563 - change done here will be propagated to both `capabilities` and `hello`
573 - change done here will be propagated to both `capabilities` and `hello`
564 command without any other action needed.
574 command without any other action needed.
565 """
575 """
566 # copy to prevent modification of the global list
576 # copy to prevent modification of the global list
567 caps = list(wireprotocaps)
577 caps = list(wireprotocaps)
568 if _allowstream(repo.ui):
578 if _allowstream(repo.ui):
569 if repo.ui.configbool('server', 'preferuncompressed', False):
579 if repo.ui.configbool('server', 'preferuncompressed', False):
570 caps.append('stream-preferred')
580 caps.append('stream-preferred')
571 requiredformats = repo.requirements & repo.supportedformats
581 requiredformats = repo.requirements & repo.supportedformats
572 # if our local revlogs are just revlogv1, add 'stream' cap
582 # if our local revlogs are just revlogv1, add 'stream' cap
573 if not requiredformats - set(('revlogv1',)):
583 if not requiredformats - set(('revlogv1',)):
574 caps.append('stream')
584 caps.append('stream')
575 # otherwise, add 'streamreqs' detailing our local revlog format
585 # otherwise, add 'streamreqs' detailing our local revlog format
576 else:
586 else:
577 caps.append('streamreqs=%s' % ','.join(requiredformats))
587 caps.append('streamreqs=%s' % ','.join(requiredformats))
578 if repo.ui.configbool('server', 'bundle2', False):
588 if repo.ui.configbool('server', 'bundle2', False):
579 caps.append('bundle2')
589 caps.append('bundle2')
580 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
590 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
581 caps.append('httpheader=1024')
591 caps.append('httpheader=1024')
582 return caps
592 return caps
583
593
584 # If you are writing an extension and consider wrapping this function. Wrap
594 # If you are writing an extension and consider wrapping this function. Wrap
585 # `_capabilities` instead.
595 # `_capabilities` instead.
586 @wireprotocommand('capabilities')
596 @wireprotocommand('capabilities')
587 def capabilities(repo, proto):
597 def capabilities(repo, proto):
588 return ' '.join(_capabilities(repo, proto))
598 return ' '.join(_capabilities(repo, proto))
589
599
590 @wireprotocommand('changegroup', 'roots')
600 @wireprotocommand('changegroup', 'roots')
591 def changegroup(repo, proto, roots):
601 def changegroup(repo, proto, roots):
592 nodes = decodelist(roots)
602 nodes = decodelist(roots)
593 cg = changegroupmod.changegroup(repo, nodes, 'serve')
603 cg = changegroupmod.changegroup(repo, nodes, 'serve')
594 return streamres(proto.groupchunks(cg))
604 return streamres(proto.groupchunks(cg))
595
605
596 @wireprotocommand('changegroupsubset', 'bases heads')
606 @wireprotocommand('changegroupsubset', 'bases heads')
597 def changegroupsubset(repo, proto, bases, heads):
607 def changegroupsubset(repo, proto, bases, heads):
598 bases = decodelist(bases)
608 bases = decodelist(bases)
599 heads = decodelist(heads)
609 heads = decodelist(heads)
600 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
610 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
601 return streamres(proto.groupchunks(cg))
611 return streamres(proto.groupchunks(cg))
602
612
603 @wireprotocommand('debugwireargs', 'one two *')
613 @wireprotocommand('debugwireargs', 'one two *')
604 def debugwireargs(repo, proto, one, two, others):
614 def debugwireargs(repo, proto, one, two, others):
605 # only accept optional args from the known set
615 # only accept optional args from the known set
606 opts = options('debugwireargs', ['three', 'four'], others)
616 opts = options('debugwireargs', ['three', 'four'], others)
607 return repo.debugwireargs(one, two, **opts)
617 return repo.debugwireargs(one, two, **opts)
608
618
609 @wireprotocommand('getbundle', '*')
619 @wireprotocommand('getbundle', '*')
610 def getbundle(repo, proto, others):
620 def getbundle(repo, proto, others):
611 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
621 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
612 for k, v in opts.iteritems():
622 for k, v in opts.iteritems():
613 if k in ('heads', 'common'):
623 if k in ('heads', 'common'):
614 opts[k] = decodelist(v)
624 opts[k] = decodelist(v)
615 elif k == 'bundlecaps':
625 elif k == 'bundlecaps':
616 opts[k] = set(v.split(','))
626 opts[k] = set(v.split(','))
617 cg = exchange.getbundle(repo, 'serve', **opts)
627 cg = exchange.getbundle(repo, 'serve', **opts)
618 return streamres(proto.groupchunks(cg))
628 return streamres(proto.groupchunks(cg))
619
629
620 @wireprotocommand('heads')
630 @wireprotocommand('heads')
621 def heads(repo, proto):
631 def heads(repo, proto):
622 h = repo.heads()
632 h = repo.heads()
623 return encodelist(h) + "\n"
633 return encodelist(h) + "\n"
624
634
625 @wireprotocommand('hello')
635 @wireprotocommand('hello')
626 def hello(repo, proto):
636 def hello(repo, proto):
627 '''the hello command returns a set of lines describing various
637 '''the hello command returns a set of lines describing various
628 interesting things about the server, in an RFC822-like format.
638 interesting things about the server, in an RFC822-like format.
629 Currently the only one defined is "capabilities", which
639 Currently the only one defined is "capabilities", which
630 consists of a line in the form:
640 consists of a line in the form:
631
641
632 capabilities: space separated list of tokens
642 capabilities: space separated list of tokens
633 '''
643 '''
634 return "capabilities: %s\n" % (capabilities(repo, proto))
644 return "capabilities: %s\n" % (capabilities(repo, proto))
635
645
636 @wireprotocommand('listkeys', 'namespace')
646 @wireprotocommand('listkeys', 'namespace')
637 def listkeys(repo, proto, namespace):
647 def listkeys(repo, proto, namespace):
638 d = repo.listkeys(encoding.tolocal(namespace)).items()
648 d = repo.listkeys(encoding.tolocal(namespace)).items()
639 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
649 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
640 for k, v in d])
650 for k, v in d])
641 return t
651 return t
642
652
643 @wireprotocommand('lookup', 'key')
653 @wireprotocommand('lookup', 'key')
644 def lookup(repo, proto, key):
654 def lookup(repo, proto, key):
645 try:
655 try:
646 k = encoding.tolocal(key)
656 k = encoding.tolocal(key)
647 c = repo[k]
657 c = repo[k]
648 r = c.hex()
658 r = c.hex()
649 success = 1
659 success = 1
650 except Exception, inst:
660 except Exception, inst:
651 r = str(inst)
661 r = str(inst)
652 success = 0
662 success = 0
653 return "%s %s\n" % (success, r)
663 return "%s %s\n" % (success, r)
654
664
655 @wireprotocommand('known', 'nodes *')
665 @wireprotocommand('known', 'nodes *')
656 def known(repo, proto, nodes, others):
666 def known(repo, proto, nodes, others):
657 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
667 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
658
668
659 @wireprotocommand('pushkey', 'namespace key old new')
669 @wireprotocommand('pushkey', 'namespace key old new')
660 def pushkey(repo, proto, namespace, key, old, new):
670 def pushkey(repo, proto, namespace, key, old, new):
661 # compatibility with pre-1.8 clients which were accidentally
671 # compatibility with pre-1.8 clients which were accidentally
662 # sending raw binary nodes rather than utf-8-encoded hex
672 # sending raw binary nodes rather than utf-8-encoded hex
663 if len(new) == 20 and new.encode('string-escape') != new:
673 if len(new) == 20 and new.encode('string-escape') != new:
664 # looks like it could be a binary node
674 # looks like it could be a binary node
665 try:
675 try:
666 new.decode('utf-8')
676 new.decode('utf-8')
667 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
677 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
668 except UnicodeDecodeError:
678 except UnicodeDecodeError:
669 pass # binary, leave unmodified
679 pass # binary, leave unmodified
670 else:
680 else:
671 new = encoding.tolocal(new) # normal path
681 new = encoding.tolocal(new) # normal path
672
682
673 if util.safehasattr(proto, 'restore'):
683 if util.safehasattr(proto, 'restore'):
674
684
675 proto.redirect()
685 proto.redirect()
676
686
677 try:
687 try:
678 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
688 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
679 encoding.tolocal(old), new) or False
689 encoding.tolocal(old), new) or False
680 except util.Abort:
690 except util.Abort:
681 r = False
691 r = False
682
692
683 output = proto.restore()
693 output = proto.restore()
684
694
685 return '%s\n%s' % (int(r), output)
695 return '%s\n%s' % (int(r), output)
686
696
687 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
697 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
688 encoding.tolocal(old), new)
698 encoding.tolocal(old), new)
689 return '%s\n' % int(r)
699 return '%s\n' % int(r)
690
700
691 def _allowstream(ui):
701 def _allowstream(ui):
692 return ui.configbool('server', 'uncompressed', True, untrusted=True)
702 return ui.configbool('server', 'uncompressed', True, untrusted=True)
693
703
694 def _walkstreamfiles(repo):
704 def _walkstreamfiles(repo):
695 # this is it's own function so extensions can override it
705 # this is it's own function so extensions can override it
696 return repo.store.walk()
706 return repo.store.walk()
697
707
698 @wireprotocommand('stream_out')
708 @wireprotocommand('stream_out')
699 def stream(repo, proto):
709 def stream(repo, proto):
700 '''If the server supports streaming clone, it advertises the "stream"
710 '''If the server supports streaming clone, it advertises the "stream"
701 capability with a value representing the version and flags of the repo
711 capability with a value representing the version and flags of the repo
702 it is serving. Client checks to see if it understands the format.
712 it is serving. Client checks to see if it understands the format.
703
713
704 The format is simple: the server writes out a line with the amount
714 The format is simple: the server writes out a line with the amount
705 of files, then the total amount of bytes to be transferred (separated
715 of files, then the total amount of bytes to be transferred (separated
706 by a space). Then, for each file, the server first writes the filename
716 by a space). Then, for each file, the server first writes the filename
707 and file size (separated by the null character), then the file contents.
717 and file size (separated by the null character), then the file contents.
708 '''
718 '''
709
719
710 if not _allowstream(repo.ui):
720 if not _allowstream(repo.ui):
711 return '1\n'
721 return '1\n'
712
722
713 entries = []
723 entries = []
714 total_bytes = 0
724 total_bytes = 0
715 try:
725 try:
716 # get consistent snapshot of repo, lock during scan
726 # get consistent snapshot of repo, lock during scan
717 lock = repo.lock()
727 lock = repo.lock()
718 try:
728 try:
719 repo.ui.debug('scanning\n')
729 repo.ui.debug('scanning\n')
720 for name, ename, size in _walkstreamfiles(repo):
730 for name, ename, size in _walkstreamfiles(repo):
721 if size:
731 if size:
722 entries.append((name, size))
732 entries.append((name, size))
723 total_bytes += size
733 total_bytes += size
724 finally:
734 finally:
725 lock.release()
735 lock.release()
726 except error.LockError:
736 except error.LockError:
727 return '2\n' # error: 2
737 return '2\n' # error: 2
728
738
729 def streamer(repo, entries, total):
739 def streamer(repo, entries, total):
730 '''stream out all metadata files in repository.'''
740 '''stream out all metadata files in repository.'''
731 yield '0\n' # success
741 yield '0\n' # success
732 repo.ui.debug('%d files, %d bytes to transfer\n' %
742 repo.ui.debug('%d files, %d bytes to transfer\n' %
733 (len(entries), total_bytes))
743 (len(entries), total_bytes))
734 yield '%d %d\n' % (len(entries), total_bytes)
744 yield '%d %d\n' % (len(entries), total_bytes)
735
745
736 sopener = repo.sopener
746 sopener = repo.sopener
737 oldaudit = sopener.mustaudit
747 oldaudit = sopener.mustaudit
738 debugflag = repo.ui.debugflag
748 debugflag = repo.ui.debugflag
739 sopener.mustaudit = False
749 sopener.mustaudit = False
740
750
741 try:
751 try:
742 for name, size in entries:
752 for name, size in entries:
743 if debugflag:
753 if debugflag:
744 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
754 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
745 # partially encode name over the wire for backwards compat
755 # partially encode name over the wire for backwards compat
746 yield '%s\0%d\n' % (store.encodedir(name), size)
756 yield '%s\0%d\n' % (store.encodedir(name), size)
747 if size <= 65536:
757 if size <= 65536:
748 fp = sopener(name)
758 fp = sopener(name)
749 try:
759 try:
750 data = fp.read(size)
760 data = fp.read(size)
751 finally:
761 finally:
752 fp.close()
762 fp.close()
753 yield data
763 yield data
754 else:
764 else:
755 for chunk in util.filechunkiter(sopener(name), limit=size):
765 for chunk in util.filechunkiter(sopener(name), limit=size):
756 yield chunk
766 yield chunk
757 # replace with "finally:" when support for python 2.4 has been dropped
767 # replace with "finally:" when support for python 2.4 has been dropped
758 except Exception:
768 except Exception:
759 sopener.mustaudit = oldaudit
769 sopener.mustaudit = oldaudit
760 raise
770 raise
761 sopener.mustaudit = oldaudit
771 sopener.mustaudit = oldaudit
762
772
763 return streamres(streamer(repo, entries, total_bytes))
773 return streamres(streamer(repo, entries, total_bytes))
764
774
765 @wireprotocommand('unbundle', 'heads')
775 @wireprotocommand('unbundle', 'heads')
766 def unbundle(repo, proto, heads):
776 def unbundle(repo, proto, heads):
767 their_heads = decodelist(heads)
777 their_heads = decodelist(heads)
768
778
769 try:
779 try:
770 proto.redirect()
780 proto.redirect()
771
781
772 exchange.check_heads(repo, their_heads, 'preparing changes')
782 exchange.check_heads(repo, their_heads, 'preparing changes')
773
783
774 # write bundle data to temporary file because it can be big
784 # write bundle data to temporary file because it can be big
775 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
785 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
776 fp = os.fdopen(fd, 'wb+')
786 fp = os.fdopen(fd, 'wb+')
777 r = 0
787 r = 0
778 try:
788 try:
779 proto.getfile(fp)
789 proto.getfile(fp)
780 fp.seek(0)
790 fp.seek(0)
781 gen = exchange.readbundle(repo.ui, fp, None)
791 gen = exchange.readbundle(repo.ui, fp, None)
782 r = exchange.unbundle(repo, gen, their_heads, 'serve',
792 r = exchange.unbundle(repo, gen, their_heads, 'serve',
783 proto._client())
793 proto._client())
794 if util.safehasattr(r, 'addpart'):
795 # The return looks streameable, we are in the bundle2 case and
796 # should return a stream.
797 return streamres(r.getchunks())
784 return pushres(r)
798 return pushres(r)
785
799
786 finally:
800 finally:
787 fp.close()
801 fp.close()
788 os.unlink(tempname)
802 os.unlink(tempname)
789 except util.Abort, inst:
803 except util.Abort, inst:
790 # The old code we moved used sys.stderr directly.
804 # The old code we moved used sys.stderr directly.
791 # We did not change it to minimise code change.
805 # We did not change it to minimise code change.
792 # This need to be moved to something proper.
806 # This need to be moved to something proper.
793 # Feel free to do it.
807 # Feel free to do it.
794 sys.stderr.write("abort: %s\n" % inst)
808 sys.stderr.write("abort: %s\n" % inst)
795 return pushres(0)
809 return pushres(0)
796 except exchange.PushRaced, exc:
810 except exchange.PushRaced, exc:
797 return pusherr(str(exc))
811 return pusherr(str(exc))
@@ -1,714 +1,785 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]
159 > [ui]
160 > ssh=python "$TESTDIR/dummyssh"
160 > ssh=python "$TESTDIR/dummyssh"
161 > [web]
162 > push_ssl = false
163 > allow_push = *
161 > EOF
164 > EOF
162
165
163 The extension requires a repo (currently unused)
166 The extension requires a repo (currently unused)
164
167
165 $ hg init main
168 $ hg init main
166 $ cd main
169 $ cd main
167 $ touch a
170 $ touch a
168 $ hg add a
171 $ hg add a
169 $ hg commit -m 'a'
172 $ hg commit -m 'a'
170
173
171
174
172 Empty bundle
175 Empty bundle
173 =================
176 =================
174
177
175 - no option
178 - no option
176 - no parts
179 - no parts
177
180
178 Test bundling
181 Test bundling
179
182
180 $ hg bundle2
183 $ hg bundle2
181 HG20\x00\x00\x00\x00 (no-eol) (esc)
184 HG20\x00\x00\x00\x00 (no-eol) (esc)
182
185
183 Test unbundling
186 Test unbundling
184
187
185 $ hg bundle2 | hg statbundle2
188 $ hg bundle2 | hg statbundle2
186 options count: 0
189 options count: 0
187 parts count: 0
190 parts count: 0
188
191
189 Test old style bundle are detected and refused
192 Test old style bundle are detected and refused
190
193
191 $ hg bundle --all ../bundle.hg
194 $ hg bundle --all ../bundle.hg
192 1 changesets found
195 1 changesets found
193 $ hg statbundle2 < ../bundle.hg
196 $ hg statbundle2 < ../bundle.hg
194 abort: unknown bundle version 10
197 abort: unknown bundle version 10
195 [255]
198 [255]
196
199
197 Test parameters
200 Test parameters
198 =================
201 =================
199
202
200 - some options
203 - some options
201 - no parts
204 - no parts
202
205
203 advisory parameters, no value
206 advisory parameters, no value
204 -------------------------------
207 -------------------------------
205
208
206 Simplest possible parameters form
209 Simplest possible parameters form
207
210
208 Test generation simple option
211 Test generation simple option
209
212
210 $ hg bundle2 --param 'caution'
213 $ hg bundle2 --param 'caution'
211 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
214 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
212
215
213 Test unbundling
216 Test unbundling
214
217
215 $ hg bundle2 --param 'caution' | hg statbundle2
218 $ hg bundle2 --param 'caution' | hg statbundle2
216 options count: 1
219 options count: 1
217 - caution
220 - caution
218 parts count: 0
221 parts count: 0
219
222
220 Test generation multiple option
223 Test generation multiple option
221
224
222 $ hg bundle2 --param 'caution' --param 'meal'
225 $ hg bundle2 --param 'caution' --param 'meal'
223 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
226 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
224
227
225 Test unbundling
228 Test unbundling
226
229
227 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
230 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
228 options count: 2
231 options count: 2
229 - caution
232 - caution
230 - meal
233 - meal
231 parts count: 0
234 parts count: 0
232
235
233 advisory parameters, with value
236 advisory parameters, with value
234 -------------------------------
237 -------------------------------
235
238
236 Test generation
239 Test generation
237
240
238 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
241 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
239 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
242 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
240
243
241 Test unbundling
244 Test unbundling
242
245
243 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
246 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
244 options count: 3
247 options count: 3
245 - caution
248 - caution
246 - elephants
249 - elephants
247 - meal
250 - meal
248 vegan
251 vegan
249 parts count: 0
252 parts count: 0
250
253
251 parameter with special char in value
254 parameter with special char in value
252 ---------------------------------------------------
255 ---------------------------------------------------
253
256
254 Test generation
257 Test generation
255
258
256 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
259 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
257 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
260 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
258
261
259 Test unbundling
262 Test unbundling
260
263
261 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
264 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
262 options count: 2
265 options count: 2
263 - e|! 7/
266 - e|! 7/
264 babar%#==tutu
267 babar%#==tutu
265 - simple
268 - simple
266 parts count: 0
269 parts count: 0
267
270
268 Test unknown mandatory option
271 Test unknown mandatory option
269 ---------------------------------------------------
272 ---------------------------------------------------
270
273
271 $ hg bundle2 --param 'Gravity' | hg statbundle2
274 $ hg bundle2 --param 'Gravity' | hg statbundle2
272 abort: unknown parameters: 'Gravity'
275 abort: unknown parameters: 'Gravity'
273 [255]
276 [255]
274
277
275 Test debug output
278 Test debug output
276 ---------------------------------------------------
279 ---------------------------------------------------
277
280
278 bundling debug
281 bundling debug
279
282
280 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
283 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
281 start emission of HG20 stream
284 start emission of HG20 stream
282 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
285 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
283 start of parts
286 start of parts
284 end of bundle
287 end of bundle
285
288
286 file content is ok
289 file content is ok
287
290
288 $ cat ../out.hg2
291 $ cat ../out.hg2
289 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
292 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
290
293
291 unbundling debug
294 unbundling debug
292
295
293 $ hg statbundle2 --debug < ../out.hg2
296 $ hg statbundle2 --debug < ../out.hg2
294 start processing of HG20 stream
297 start processing of HG20 stream
295 reading bundle2 stream parameters
298 reading bundle2 stream parameters
296 ignoring unknown parameter 'e|! 7/'
299 ignoring unknown parameter 'e|! 7/'
297 ignoring unknown parameter 'simple'
300 ignoring unknown parameter 'simple'
298 options count: 2
301 options count: 2
299 - e|! 7/
302 - e|! 7/
300 babar%#==tutu
303 babar%#==tutu
301 - simple
304 - simple
302 start extraction of bundle2 parts
305 start extraction of bundle2 parts
303 part header size: 0
306 part header size: 0
304 end of bundle2 stream
307 end of bundle2 stream
305 parts count: 0
308 parts count: 0
306
309
307
310
308 Test buggy input
311 Test buggy input
309 ---------------------------------------------------
312 ---------------------------------------------------
310
313
311 empty parameter name
314 empty parameter name
312
315
313 $ hg bundle2 --param '' --quiet
316 $ hg bundle2 --param '' --quiet
314 abort: empty parameter name
317 abort: empty parameter name
315 [255]
318 [255]
316
319
317 bad parameter name
320 bad parameter name
318
321
319 $ hg bundle2 --param 42babar
322 $ hg bundle2 --param 42babar
320 abort: non letter first character: '42babar'
323 abort: non letter first character: '42babar'
321 [255]
324 [255]
322
325
323
326
324 Test part
327 Test part
325 =================
328 =================
326
329
327 $ hg bundle2 --parts ../parts.hg2 --debug
330 $ hg bundle2 --parts ../parts.hg2 --debug
328 start emission of HG20 stream
331 start emission of HG20 stream
329 bundle parameter:
332 bundle parameter:
330 start of parts
333 start of parts
331 bundle part: "test:empty"
334 bundle part: "test:empty"
332 bundle part: "test:empty"
335 bundle part: "test:empty"
333 bundle part: "test:song"
336 bundle part: "test:song"
334 bundle part: "test:math"
337 bundle part: "test:math"
335 bundle part: "test:ping"
338 bundle part: "test:ping"
336 end of bundle
339 end of bundle
337
340
338 $ cat ../parts.hg2
341 $ cat ../parts.hg2
339 HG20\x00\x00\x00\x11 (esc)
342 HG20\x00\x00\x00\x11 (esc)
340 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
343 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (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)
344 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)
342 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
345 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
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)
346 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)
344
347
345
348
346 $ hg statbundle2 < ../parts.hg2
349 $ hg statbundle2 < ../parts.hg2
347 options count: 0
350 options count: 0
348 :test:empty:
351 :test:empty:
349 mandatory: 0
352 mandatory: 0
350 advisory: 0
353 advisory: 0
351 payload: 0 bytes
354 payload: 0 bytes
352 :test:empty:
355 :test:empty:
353 mandatory: 0
356 mandatory: 0
354 advisory: 0
357 advisory: 0
355 payload: 0 bytes
358 payload: 0 bytes
356 :test:song:
359 :test:song:
357 mandatory: 0
360 mandatory: 0
358 advisory: 0
361 advisory: 0
359 payload: 178 bytes
362 payload: 178 bytes
360 :test:math:
363 :test:math:
361 mandatory: 2
364 mandatory: 2
362 advisory: 1
365 advisory: 1
363 payload: 2 bytes
366 payload: 2 bytes
364 :test:ping:
367 :test:ping:
365 mandatory: 0
368 mandatory: 0
366 advisory: 0
369 advisory: 0
367 payload: 0 bytes
370 payload: 0 bytes
368 parts count: 5
371 parts count: 5
369
372
370 $ hg statbundle2 --debug < ../parts.hg2
373 $ hg statbundle2 --debug < ../parts.hg2
371 start processing of HG20 stream
374 start processing of HG20 stream
372 reading bundle2 stream parameters
375 reading bundle2 stream parameters
373 options count: 0
376 options count: 0
374 start extraction of bundle2 parts
377 start extraction of bundle2 parts
375 part header size: 17
378 part header size: 17
376 part type: "test:empty"
379 part type: "test:empty"
377 part id: "0"
380 part id: "0"
378 part parameters: 0
381 part parameters: 0
379 :test:empty:
382 :test:empty:
380 mandatory: 0
383 mandatory: 0
381 advisory: 0
384 advisory: 0
382 payload chunk size: 0
385 payload chunk size: 0
383 payload: 0 bytes
386 payload: 0 bytes
384 part header size: 17
387 part header size: 17
385 part type: "test:empty"
388 part type: "test:empty"
386 part id: "1"
389 part id: "1"
387 part parameters: 0
390 part parameters: 0
388 :test:empty:
391 :test:empty:
389 mandatory: 0
392 mandatory: 0
390 advisory: 0
393 advisory: 0
391 payload chunk size: 0
394 payload chunk size: 0
392 payload: 0 bytes
395 payload: 0 bytes
393 part header size: 16
396 part header size: 16
394 part type: "test:song"
397 part type: "test:song"
395 part id: "2"
398 part id: "2"
396 part parameters: 0
399 part parameters: 0
397 :test:song:
400 :test:song:
398 mandatory: 0
401 mandatory: 0
399 advisory: 0
402 advisory: 0
400 payload chunk size: 178
403 payload chunk size: 178
401 payload chunk size: 0
404 payload chunk size: 0
402 payload: 178 bytes
405 payload: 178 bytes
403 part header size: 43
406 part header size: 43
404 part type: "test:math"
407 part type: "test:math"
405 part id: "3"
408 part id: "3"
406 part parameters: 3
409 part parameters: 3
407 :test:math:
410 :test:math:
408 mandatory: 2
411 mandatory: 2
409 advisory: 1
412 advisory: 1
410 payload chunk size: 2
413 payload chunk size: 2
411 payload chunk size: 0
414 payload chunk size: 0
412 payload: 2 bytes
415 payload: 2 bytes
413 part header size: 16
416 part header size: 16
414 part type: "test:ping"
417 part type: "test:ping"
415 part id: "4"
418 part id: "4"
416 part parameters: 0
419 part parameters: 0
417 :test:ping:
420 :test:ping:
418 mandatory: 0
421 mandatory: 0
419 advisory: 0
422 advisory: 0
420 payload chunk size: 0
423 payload chunk size: 0
421 payload: 0 bytes
424 payload: 0 bytes
422 part header size: 0
425 part header size: 0
423 end of bundle2 stream
426 end of bundle2 stream
424 parts count: 5
427 parts count: 5
425
428
426 Test actual unbundling of test part
429 Test actual unbundling of test part
427 =======================================
430 =======================================
428
431
429 Process the bundle
432 Process the bundle
430
433
431 $ hg unbundle2 --debug < ../parts.hg2
434 $ hg unbundle2 --debug < ../parts.hg2
432 start processing of HG20 stream
435 start processing of HG20 stream
433 reading bundle2 stream parameters
436 reading bundle2 stream parameters
434 start extraction of bundle2 parts
437 start extraction of bundle2 parts
435 part header size: 17
438 part header size: 17
436 part type: "test:empty"
439 part type: "test:empty"
437 part id: "0"
440 part id: "0"
438 part parameters: 0
441 part parameters: 0
439 ignoring unknown advisory part 'test:empty'
442 ignoring unknown advisory part 'test:empty'
440 payload chunk size: 0
443 payload chunk size: 0
441 part header size: 17
444 part header size: 17
442 part type: "test:empty"
445 part type: "test:empty"
443 part id: "1"
446 part id: "1"
444 part parameters: 0
447 part parameters: 0
445 ignoring unknown advisory part 'test:empty'
448 ignoring unknown advisory part 'test:empty'
446 payload chunk size: 0
449 payload chunk size: 0
447 part header size: 16
450 part header size: 16
448 part type: "test:song"
451 part type: "test:song"
449 part id: "2"
452 part id: "2"
450 part parameters: 0
453 part parameters: 0
451 found a handler for part 'test:song'
454 found a handler for part 'test:song'
452 The choir starts singing:
455 The choir starts singing:
453 payload chunk size: 178
456 payload chunk size: 178
454 payload chunk size: 0
457 payload chunk size: 0
455 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
458 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
456 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
459 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
457 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
460 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
458 part header size: 43
461 part header size: 43
459 part type: "test:math"
462 part type: "test:math"
460 part id: "3"
463 part id: "3"
461 part parameters: 3
464 part parameters: 3
462 ignoring unknown advisory part 'test:math'
465 ignoring unknown advisory part 'test:math'
463 payload chunk size: 2
466 payload chunk size: 2
464 payload chunk size: 0
467 payload chunk size: 0
465 part header size: 16
468 part header size: 16
466 part type: "test:ping"
469 part type: "test:ping"
467 part id: "4"
470 part id: "4"
468 part parameters: 0
471 part parameters: 0
469 found a handler for part 'test:ping'
472 found a handler for part 'test:ping'
470 received ping request (id 4)
473 received ping request (id 4)
471 payload chunk size: 0
474 payload chunk size: 0
472 part header size: 0
475 part header size: 0
473 end of bundle2 stream
476 end of bundle2 stream
474 0 unread bytes
477 0 unread bytes
475 3 total verses sung
478 3 total verses sung
476
479
477 Unbundle with an unknown mandatory part
480 Unbundle with an unknown mandatory part
478 (should abort)
481 (should abort)
479
482
480 $ hg bundle2 --parts --unknown ../unknown.hg2
483 $ hg bundle2 --parts --unknown ../unknown.hg2
481
484
482 $ hg unbundle2 < ../unknown.hg2
485 $ hg unbundle2 < ../unknown.hg2
483 The choir starts singing:
486 The choir starts singing:
484 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
487 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
485 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
488 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
486 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
489 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
487 0 unread bytes
490 0 unread bytes
488 abort: missing support for 'test:unknown'
491 abort: missing support for 'test:unknown'
489 [255]
492 [255]
490
493
491 unbundle with a reply
494 unbundle with a reply
492
495
493 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
496 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
494 The choir starts singing:
497 The choir starts singing:
495 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
498 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
496 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
499 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
497 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
500 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
498 received ping request (id 4)
501 received ping request (id 4)
499 0 unread bytes
502 0 unread bytes
500 3 total verses sung
503 3 total verses sung
501
504
502 The reply is a bundle
505 The reply is a bundle
503
506
504 $ cat ../reply.hg2
507 $ cat ../reply.hg2
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)
508 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)
506
509
507 The reply is valid
510 The reply is valid
508
511
509 $ hg statbundle2 < ../reply.hg2
512 $ hg statbundle2 < ../reply.hg2
510 options count: 0
513 options count: 0
511 :test:pong:
514 :test:pong:
512 mandatory: 1
515 mandatory: 1
513 advisory: 0
516 advisory: 0
514 payload: 0 bytes
517 payload: 0 bytes
515 parts count: 1
518 parts count: 1
516
519
517 Support for changegroup
520 Support for changegroup
518 ===================================
521 ===================================
519
522
520 $ hg unbundle $TESTDIR/bundles/rebase.hg
523 $ hg unbundle $TESTDIR/bundles/rebase.hg
521 adding changesets
524 adding changesets
522 adding manifests
525 adding manifests
523 adding file changes
526 adding file changes
524 added 8 changesets with 7 changes to 7 files (+3 heads)
527 added 8 changesets with 7 changes to 7 files (+3 heads)
525 (run 'hg heads' to see heads, 'hg merge' to merge)
528 (run 'hg heads' to see heads, 'hg merge' to merge)
526
529
527 $ hg log -G
530 $ hg log -G
528 o changeset: 8:02de42196ebe
531 o changeset: 8:02de42196ebe
529 | tag: tip
532 | tag: tip
530 | parent: 6:24b6387c8c8c
533 | parent: 6:24b6387c8c8c
531 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
534 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
532 | date: Sat Apr 30 15:24:48 2011 +0200
535 | date: Sat Apr 30 15:24:48 2011 +0200
533 | summary: H
536 | summary: H
534 |
537 |
535 | o changeset: 7:eea13746799a
538 | o changeset: 7:eea13746799a
536 |/| parent: 6:24b6387c8c8c
539 |/| parent: 6:24b6387c8c8c
537 | | parent: 5:9520eea781bc
540 | | parent: 5:9520eea781bc
538 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
541 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
539 | | date: Sat Apr 30 15:24:48 2011 +0200
542 | | date: Sat Apr 30 15:24:48 2011 +0200
540 | | summary: G
543 | | summary: G
541 | |
544 | |
542 o | changeset: 6:24b6387c8c8c
545 o | changeset: 6:24b6387c8c8c
543 | | parent: 1:cd010b8cd998
546 | | parent: 1:cd010b8cd998
544 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
547 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
545 | | date: Sat Apr 30 15:24:48 2011 +0200
548 | | date: Sat Apr 30 15:24:48 2011 +0200
546 | | summary: F
549 | | summary: F
547 | |
550 | |
548 | o changeset: 5:9520eea781bc
551 | o changeset: 5:9520eea781bc
549 |/ parent: 1:cd010b8cd998
552 |/ parent: 1:cd010b8cd998
550 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
553 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
551 | date: Sat Apr 30 15:24:48 2011 +0200
554 | date: Sat Apr 30 15:24:48 2011 +0200
552 | summary: E
555 | summary: E
553 |
556 |
554 | o changeset: 4:32af7686d403
557 | o changeset: 4:32af7686d403
555 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
558 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
556 | | date: Sat Apr 30 15:24:48 2011 +0200
559 | | date: Sat Apr 30 15:24:48 2011 +0200
557 | | summary: D
560 | | summary: D
558 | |
561 | |
559 | o changeset: 3:5fddd98957c8
562 | o changeset: 3:5fddd98957c8
560 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
563 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
561 | | date: Sat Apr 30 15:24:48 2011 +0200
564 | | date: Sat Apr 30 15:24:48 2011 +0200
562 | | summary: C
565 | | summary: C
563 | |
566 | |
564 | o changeset: 2:42ccdea3bb16
567 | o changeset: 2:42ccdea3bb16
565 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
568 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
566 | date: Sat Apr 30 15:24:48 2011 +0200
569 | date: Sat Apr 30 15:24:48 2011 +0200
567 | summary: B
570 | summary: B
568 |
571 |
569 o changeset: 1:cd010b8cd998
572 o changeset: 1:cd010b8cd998
570 parent: -1:000000000000
573 parent: -1:000000000000
571 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
574 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
572 date: Sat Apr 30 15:24:48 2011 +0200
575 date: Sat Apr 30 15:24:48 2011 +0200
573 summary: A
576 summary: A
574
577
575 @ changeset: 0:3903775176ed
578 @ changeset: 0:3903775176ed
576 user: test
579 user: test
577 date: Thu Jan 01 00:00:00 1970 +0000
580 date: Thu Jan 01 00:00:00 1970 +0000
578 summary: a
581 summary: a
579
582
580
583
581 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
584 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
582 4 changesets found
585 4 changesets found
583 list of changesets:
586 list of changesets:
584 32af7686d403cf45b5d95f2d70cebea587ac806a
587 32af7686d403cf45b5d95f2d70cebea587ac806a
585 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
588 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
586 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
589 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
587 02de42196ebee42ef284b6780a87cdc96e8eaab6
590 02de42196ebee42ef284b6780a87cdc96e8eaab6
588 start emission of HG20 stream
591 start emission of HG20 stream
589 bundle parameter:
592 bundle parameter:
590 start of parts
593 start of parts
591 bundle part: "changegroup"
594 bundle part: "changegroup"
592 bundling: 1/4 changesets (25.00%)
595 bundling: 1/4 changesets (25.00%)
593 bundling: 2/4 changesets (50.00%)
596 bundling: 2/4 changesets (50.00%)
594 bundling: 3/4 changesets (75.00%)
597 bundling: 3/4 changesets (75.00%)
595 bundling: 4/4 changesets (100.00%)
598 bundling: 4/4 changesets (100.00%)
596 bundling: 1/4 manifests (25.00%)
599 bundling: 1/4 manifests (25.00%)
597 bundling: 2/4 manifests (50.00%)
600 bundling: 2/4 manifests (50.00%)
598 bundling: 3/4 manifests (75.00%)
601 bundling: 3/4 manifests (75.00%)
599 bundling: 4/4 manifests (100.00%)
602 bundling: 4/4 manifests (100.00%)
600 bundling: D 1/3 files (33.33%)
603 bundling: D 1/3 files (33.33%)
601 bundling: E 2/3 files (66.67%)
604 bundling: E 2/3 files (66.67%)
602 bundling: H 3/3 files (100.00%)
605 bundling: H 3/3 files (100.00%)
603 end of bundle
606 end of bundle
604
607
605 $ cat ../rev.hg2
608 $ cat ../rev.hg2
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)
609 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)
607 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
610 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (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)
611 \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)
609 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
612 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (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)
613 \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)
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)
614 \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)
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)
615 \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)
613 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
616 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
614 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
617 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (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)
618 \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)
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)
619 \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)
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)
620 \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)
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)
621 \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)
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)
622 \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)
620 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
623 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (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)
624 \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)
622 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
625 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
623 l\r (no-eol) (esc)
626 l\r (no-eol) (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)
627 \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)
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)
628 \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)
626 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
629 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
627 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
630 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
628
631
629 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
632 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
630 adding changesets
633 adding changesets
631 adding manifests
634 adding manifests
632 adding file changes
635 adding file changes
633 added 0 changesets with 0 changes to 3 files
636 added 0 changesets with 0 changes to 3 files
634 0 unread bytes
637 0 unread bytes
635 addchangegroup return: 1
638 addchangegroup return: 1
636
639
637 $ cat ../rev-replay.hg2
640 $ cat ../rev-replay.hg2
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)
641 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)
639
642
640 Real world exchange
643 Real world exchange
641 =====================
644 =====================
642
645
643
646
644 clone --pull
647 clone --pull
645
648
646 $ cd ..
649 $ cd ..
647 $ hg clone main other --pull --rev 9520eea781bc
650 $ hg clone main other --pull --rev 9520eea781bc
648 adding changesets
651 adding changesets
649 adding manifests
652 adding manifests
650 adding file changes
653 adding file changes
651 added 2 changesets with 2 changes to 2 files
654 added 2 changesets with 2 changes to 2 files
652 updating to branch default
655 updating to branch default
653 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
656 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
654 $ hg -R other log -G
657 $ hg -R other log -G
655 @ changeset: 1:9520eea781bc
658 @ changeset: 1:9520eea781bc
656 | tag: tip
659 | tag: tip
657 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
660 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
658 | date: Sat Apr 30 15:24:48 2011 +0200
661 | date: Sat Apr 30 15:24:48 2011 +0200
659 | summary: E
662 | summary: E
660 |
663 |
661 o changeset: 0:cd010b8cd998
664 o changeset: 0:cd010b8cd998
662 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
665 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
663 date: Sat Apr 30 15:24:48 2011 +0200
666 date: Sat Apr 30 15:24:48 2011 +0200
664 summary: A
667 summary: A
665
668
666
669
667 pull
670 pull
668
671
669 $ hg -R other pull -r 24b6387c8c8c
672 $ hg -R other pull -r 24b6387c8c8c
670 pulling from $TESTTMP/main (glob)
673 pulling from $TESTTMP/main (glob)
671 searching for changes
674 searching for changes
672 adding changesets
675 adding changesets
673 adding manifests
676 adding manifests
674 adding file changes
677 adding file changes
675 added 1 changesets with 1 changes to 1 files (+1 heads)
678 added 1 changesets with 1 changes to 1 files (+1 heads)
676 (run 'hg heads' to see heads, 'hg merge' to merge)
679 (run 'hg heads' to see heads, 'hg merge' to merge)
677
680
678 push
681 push
679
682
680 $ hg -R main push other --rev eea13746799a
683 $ hg -R main push other --rev eea13746799a
681 pushing to other
684 pushing to other
682 searching for changes
685 searching for changes
683 adding changesets
686 adding changesets
684 adding manifests
687 adding manifests
685 adding file changes
688 adding file changes
686 added 1 changesets with 0 changes to 0 files (-1 heads)
689 added 1 changesets with 0 changes to 0 files (-1 heads)
687
690
688 pull over ssh
691 pull over ssh
689
692
690 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
693 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
691 pulling from ssh://user@dummy/main
694 pulling from ssh://user@dummy/main
692 searching for changes
695 searching for changes
693 adding changesets
696 adding changesets
694 adding manifests
697 adding manifests
695 adding file changes
698 adding file changes
696 added 1 changesets with 1 changes to 1 files (+1 heads)
699 added 1 changesets with 1 changes to 1 files (+1 heads)
697 (run 'hg heads' to see heads, 'hg merge' to merge)
700 (run 'hg heads' to see heads, 'hg merge' to merge)
698
701
699 pull over http
702 pull over http
700
703
701 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
704 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
702 $ cat main.pid >> $DAEMON_PIDS
705 $ cat main.pid >> $DAEMON_PIDS
703
706
704 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
707 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
705 pulling from http://localhost:$HGPORT/
708 pulling from http://localhost:$HGPORT/
706 searching for changes
709 searching for changes
707 adding changesets
710 adding changesets
708 adding manifests
711 adding manifests
709 adding file changes
712 adding file changes
710 added 1 changesets with 1 changes to 1 files (+1 heads)
713 added 1 changesets with 1 changes to 1 files (+1 heads)
711 (run 'hg heads .' to see heads, 'hg merge' to merge)
714 (run 'hg heads .' to see heads, 'hg merge' to merge)
712 $ cat main-error.log
715 $ cat main-error.log
713
716
717 push over ssh
714
718
719 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
720 pushing to ssh://user@dummy/other
721 searching for changes
722 remote: adding changesets
723 remote: adding manifests
724 remote: adding file changes
725 remote: added 1 changesets with 1 changes to 1 files
726
727 push over http
728
729 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
730 $ cat other.pid >> $DAEMON_PIDS
731
732 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
733 pushing to http://localhost:$HGPORT2/
734 searching for changes
735 $ cat other-error.log
736
737 Check final content.
738
739 $ hg -R other log -G
740 o changeset: 7:32af7686d403
741 | tag: tip
742 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
743 | date: Sat Apr 30 15:24:48 2011 +0200
744 | summary: D
745 |
746 o changeset: 6:5fddd98957c8
747 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
748 | date: Sat Apr 30 15:24:48 2011 +0200
749 | summary: C
750 |
751 o changeset: 5:42ccdea3bb16
752 | parent: 0:cd010b8cd998
753 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
754 | date: Sat Apr 30 15:24:48 2011 +0200
755 | summary: B
756 |
757 | o changeset: 4:02de42196ebe
758 | | parent: 2:24b6387c8c8c
759 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
760 | | date: Sat Apr 30 15:24:48 2011 +0200
761 | | summary: H
762 | |
763 | | o changeset: 3:eea13746799a
764 | |/| parent: 2:24b6387c8c8c
765 | | | parent: 1:9520eea781bc
766 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
767 | | | date: Sat Apr 30 15:24:48 2011 +0200
768 | | | summary: G
769 | | |
770 | o | changeset: 2:24b6387c8c8c
771 |/ / parent: 0:cd010b8cd998
772 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
773 | | date: Sat Apr 30 15:24:48 2011 +0200
774 | | summary: F
775 | |
776 | @ changeset: 1:9520eea781bc
777 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
778 | date: Sat Apr 30 15:24:48 2011 +0200
779 | summary: E
780 |
781 o changeset: 0:cd010b8cd998
782 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
783 date: Sat Apr 30 15:24:48 2011 +0200
784 summary: A
785
General Comments 0
You need to be logged in to leave comments. Login now