##// END OF EJS Templates
bookmarks: fix pushkey compatibility mode (issue5777)...
Boris Feld -
r35829:e35320ce stable
parent child Browse files
Show More
@@ -1,2157 +1,2157
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 24 the Binary format
25 25 ============================
26 26
27 27 All numbers are unsigned and big-endian.
28 28
29 29 stream level parameters
30 30 ------------------------
31 31
32 32 Binary format is as follow
33 33
34 34 :params size: int32
35 35
36 36 The total number of Bytes used by the parameters
37 37
38 38 :params value: arbitrary number of Bytes
39 39
40 40 A blob of `params size` containing the serialized version of all stream level
41 41 parameters.
42 42
43 43 The blob contains a space separated list of parameters. Parameters with value
44 44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45 45
46 46 Empty name are obviously forbidden.
47 47
48 48 Name MUST start with a letter. If this first letter is lower case, the
49 49 parameter is advisory and can be safely ignored. However when the first
50 50 letter is capital, the parameter is mandatory and the bundling process MUST
51 51 stop if he is not able to proceed it.
52 52
53 53 Stream parameters use a simple textual format for two main reasons:
54 54
55 55 - Stream level parameters should remain simple and we want to discourage any
56 56 crazy usage.
57 57 - Textual data allow easy human inspection of a bundle2 header in case of
58 58 troubles.
59 59
60 60 Any Applicative level options MUST go into a bundle2 part instead.
61 61
62 62 Payload part
63 63 ------------------------
64 64
65 65 Binary format is as follow
66 66
67 67 :header size: int32
68 68
69 69 The total number of Bytes used by the part header. When the header is empty
70 70 (size = 0) this is interpreted as the end of stream marker.
71 71
72 72 :header:
73 73
74 74 The header defines how to interpret the part. It contains two piece of
75 75 data: the part type, and the part parameters.
76 76
77 77 The part type is used to route an application level handler, that can
78 78 interpret payload.
79 79
80 80 Part parameters are passed to the application level handler. They are
81 81 meant to convey information that will help the application level object to
82 82 interpret the part payload.
83 83
84 84 The binary format of the header is has follow
85 85
86 86 :typesize: (one byte)
87 87
88 88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89 89
90 90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 91 to this part.
92 92
93 93 :parameters:
94 94
95 95 Part's parameter may have arbitrary content, the binary structure is::
96 96
97 97 <mandatory-count><advisory-count><param-sizes><param-data>
98 98
99 99 :mandatory-count: 1 byte, number of mandatory parameters
100 100
101 101 :advisory-count: 1 byte, number of advisory parameters
102 102
103 103 :param-sizes:
104 104
105 105 N couple of bytes, where N is the total number of parameters. Each
106 106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107 107
108 108 :param-data:
109 109
110 110 A blob of bytes from which each parameter key and value can be
111 111 retrieved using the list of size couples stored in the previous
112 112 field.
113 113
114 114 Mandatory parameters comes first, then the advisory ones.
115 115
116 116 Each parameter's key MUST be unique within the part.
117 117
118 118 :payload:
119 119
120 120 payload is a series of `<chunksize><chunkdata>`.
121 121
122 122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124 124
125 125 The current implementation always produces either zero or one chunk.
126 126 This is an implementation limitation that will ultimately be lifted.
127 127
128 128 `chunksize` can be negative to trigger special case processing. No such
129 129 processing is in place yet.
130 130
131 131 Bundle processing
132 132 ============================
133 133
134 134 Each part is processed in order using a "part handler". Handler are registered
135 135 for a certain part type.
136 136
137 137 The matching of a part to its handler is case insensitive. The case of the
138 138 part type is used to know if a part is mandatory or advisory. If the Part type
139 139 contains any uppercase char it is considered mandatory. When no handler is
140 140 known for a Mandatory part, the process is aborted and an exception is raised.
141 141 If the part is advisory and no handler is known, the part is ignored. When the
142 142 process is aborted, the full bundle is still read from the stream to keep the
143 143 channel usable. But none of the part read from an abort are processed. In the
144 144 future, dropping the stream may become an option for channel we do not care to
145 145 preserve.
146 146 """
147 147
148 148 from __future__ import absolute_import, division
149 149
150 150 import errno
151 151 import os
152 152 import re
153 153 import string
154 154 import struct
155 155 import sys
156 156
157 157 from .i18n import _
158 158 from . import (
159 159 bookmarks,
160 160 changegroup,
161 161 error,
162 162 node as nodemod,
163 163 obsolete,
164 164 phases,
165 165 pushkey,
166 166 pycompat,
167 167 streamclone,
168 168 tags,
169 169 url,
170 170 util,
171 171 )
172 172
173 173 urlerr = util.urlerr
174 174 urlreq = util.urlreq
175 175
176 176 _pack = struct.pack
177 177 _unpack = struct.unpack
178 178
179 179 _fstreamparamsize = '>i'
180 180 _fpartheadersize = '>i'
181 181 _fparttypesize = '>B'
182 182 _fpartid = '>I'
183 183 _fpayloadsize = '>i'
184 184 _fpartparamcount = '>BB'
185 185
186 186 preferedchunksize = 32768
187 187
188 188 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
189 189
190 190 def outdebug(ui, message):
191 191 """debug regarding output stream (bundling)"""
192 192 if ui.configbool('devel', 'bundle2.debug'):
193 193 ui.debug('bundle2-output: %s\n' % message)
194 194
195 195 def indebug(ui, message):
196 196 """debug on input stream (unbundling)"""
197 197 if ui.configbool('devel', 'bundle2.debug'):
198 198 ui.debug('bundle2-input: %s\n' % message)
199 199
200 200 def validateparttype(parttype):
201 201 """raise ValueError if a parttype contains invalid character"""
202 202 if _parttypeforbidden.search(parttype):
203 203 raise ValueError(parttype)
204 204
205 205 def _makefpartparamsizes(nbparams):
206 206 """return a struct format to read part parameter sizes
207 207
208 208 The number parameters is variable so we need to build that format
209 209 dynamically.
210 210 """
211 211 return '>'+('BB'*nbparams)
212 212
213 213 parthandlermapping = {}
214 214
215 215 def parthandler(parttype, params=()):
216 216 """decorator that register a function as a bundle2 part handler
217 217
218 218 eg::
219 219
220 220 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
221 221 def myparttypehandler(...):
222 222 '''process a part of type "my part".'''
223 223 ...
224 224 """
225 225 validateparttype(parttype)
226 226 def _decorator(func):
227 227 lparttype = parttype.lower() # enforce lower case matching.
228 228 assert lparttype not in parthandlermapping
229 229 parthandlermapping[lparttype] = func
230 230 func.params = frozenset(params)
231 231 return func
232 232 return _decorator
233 233
234 234 class unbundlerecords(object):
235 235 """keep record of what happens during and unbundle
236 236
237 237 New records are added using `records.add('cat', obj)`. Where 'cat' is a
238 238 category of record and obj is an arbitrary object.
239 239
240 240 `records['cat']` will return all entries of this category 'cat'.
241 241
242 242 Iterating on the object itself will yield `('category', obj)` tuples
243 243 for all entries.
244 244
245 245 All iterations happens in chronological order.
246 246 """
247 247
248 248 def __init__(self):
249 249 self._categories = {}
250 250 self._sequences = []
251 251 self._replies = {}
252 252
253 253 def add(self, category, entry, inreplyto=None):
254 254 """add a new record of a given category.
255 255
256 256 The entry can then be retrieved in the list returned by
257 257 self['category']."""
258 258 self._categories.setdefault(category, []).append(entry)
259 259 self._sequences.append((category, entry))
260 260 if inreplyto is not None:
261 261 self.getreplies(inreplyto).add(category, entry)
262 262
263 263 def getreplies(self, partid):
264 264 """get the records that are replies to a specific part"""
265 265 return self._replies.setdefault(partid, unbundlerecords())
266 266
267 267 def __getitem__(self, cat):
268 268 return tuple(self._categories.get(cat, ()))
269 269
270 270 def __iter__(self):
271 271 return iter(self._sequences)
272 272
273 273 def __len__(self):
274 274 return len(self._sequences)
275 275
276 276 def __nonzero__(self):
277 277 return bool(self._sequences)
278 278
279 279 __bool__ = __nonzero__
280 280
281 281 class bundleoperation(object):
282 282 """an object that represents a single bundling process
283 283
284 284 Its purpose is to carry unbundle-related objects and states.
285 285
286 286 A new object should be created at the beginning of each bundle processing.
287 287 The object is to be returned by the processing function.
288 288
289 289 The object has very little content now it will ultimately contain:
290 290 * an access to the repo the bundle is applied to,
291 291 * a ui object,
292 292 * a way to retrieve a transaction to add changes to the repo,
293 293 * a way to record the result of processing each part,
294 294 * a way to construct a bundle response when applicable.
295 295 """
296 296
297 297 def __init__(self, repo, transactiongetter, captureoutput=True):
298 298 self.repo = repo
299 299 self.ui = repo.ui
300 300 self.records = unbundlerecords()
301 301 self.reply = None
302 302 self.captureoutput = captureoutput
303 303 self.hookargs = {}
304 304 self._gettransaction = transactiongetter
305 305 # carries value that can modify part behavior
306 306 self.modes = {}
307 307
308 308 def gettransaction(self):
309 309 transaction = self._gettransaction()
310 310
311 311 if self.hookargs:
312 312 # the ones added to the transaction supercede those added
313 313 # to the operation.
314 314 self.hookargs.update(transaction.hookargs)
315 315 transaction.hookargs = self.hookargs
316 316
317 317 # mark the hookargs as flushed. further attempts to add to
318 318 # hookargs will result in an abort.
319 319 self.hookargs = None
320 320
321 321 return transaction
322 322
323 323 def addhookargs(self, hookargs):
324 324 if self.hookargs is None:
325 325 raise error.ProgrammingError('attempted to add hookargs to '
326 326 'operation after transaction started')
327 327 self.hookargs.update(hookargs)
328 328
329 329 class TransactionUnavailable(RuntimeError):
330 330 pass
331 331
332 332 def _notransaction():
333 333 """default method to get a transaction while processing a bundle
334 334
335 335 Raise an exception to highlight the fact that no transaction was expected
336 336 to be created"""
337 337 raise TransactionUnavailable()
338 338
339 339 def applybundle(repo, unbundler, tr, source=None, url=None, **kwargs):
340 340 # transform me into unbundler.apply() as soon as the freeze is lifted
341 341 if isinstance(unbundler, unbundle20):
342 342 tr.hookargs['bundle2'] = '1'
343 343 if source is not None and 'source' not in tr.hookargs:
344 344 tr.hookargs['source'] = source
345 345 if url is not None and 'url' not in tr.hookargs:
346 346 tr.hookargs['url'] = url
347 347 return processbundle(repo, unbundler, lambda: tr)
348 348 else:
349 349 # the transactiongetter won't be used, but we might as well set it
350 350 op = bundleoperation(repo, lambda: tr)
351 351 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
352 352 return op
353 353
354 354 class partiterator(object):
355 355 def __init__(self, repo, op, unbundler):
356 356 self.repo = repo
357 357 self.op = op
358 358 self.unbundler = unbundler
359 359 self.iterator = None
360 360 self.count = 0
361 361 self.current = None
362 362
363 363 def __enter__(self):
364 364 def func():
365 365 itr = enumerate(self.unbundler.iterparts())
366 366 for count, p in itr:
367 367 self.count = count
368 368 self.current = p
369 369 yield p
370 370 p.consume()
371 371 self.current = None
372 372 self.iterator = func()
373 373 return self.iterator
374 374
375 375 def __exit__(self, type, exc, tb):
376 376 if not self.iterator:
377 377 return
378 378
379 379 # Only gracefully abort in a normal exception situation. User aborts
380 380 # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
381 381 # and should not gracefully cleanup.
382 382 if isinstance(exc, Exception):
383 383 # Any exceptions seeking to the end of the bundle at this point are
384 384 # almost certainly related to the underlying stream being bad.
385 385 # And, chances are that the exception we're handling is related to
386 386 # getting in that bad state. So, we swallow the seeking error and
387 387 # re-raise the original error.
388 388 seekerror = False
389 389 try:
390 390 if self.current:
391 391 # consume the part content to not corrupt the stream.
392 392 self.current.consume()
393 393
394 394 for part in self.iterator:
395 395 # consume the bundle content
396 396 part.consume()
397 397 except Exception:
398 398 seekerror = True
399 399
400 400 # Small hack to let caller code distinguish exceptions from bundle2
401 401 # processing from processing the old format. This is mostly needed
402 402 # to handle different return codes to unbundle according to the type
403 403 # of bundle. We should probably clean up or drop this return code
404 404 # craziness in a future version.
405 405 exc.duringunbundle2 = True
406 406 salvaged = []
407 407 replycaps = None
408 408 if self.op.reply is not None:
409 409 salvaged = self.op.reply.salvageoutput()
410 410 replycaps = self.op.reply.capabilities
411 411 exc._replycaps = replycaps
412 412 exc._bundle2salvagedoutput = salvaged
413 413
414 414 # Re-raising from a variable loses the original stack. So only use
415 415 # that form if we need to.
416 416 if seekerror:
417 417 raise exc
418 418
419 419 self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
420 420 self.count)
421 421
422 422 def processbundle(repo, unbundler, transactiongetter=None, op=None):
423 423 """This function process a bundle, apply effect to/from a repo
424 424
425 425 It iterates over each part then searches for and uses the proper handling
426 426 code to process the part. Parts are processed in order.
427 427
428 428 Unknown Mandatory part will abort the process.
429 429
430 430 It is temporarily possible to provide a prebuilt bundleoperation to the
431 431 function. This is used to ensure output is properly propagated in case of
432 432 an error during the unbundling. This output capturing part will likely be
433 433 reworked and this ability will probably go away in the process.
434 434 """
435 435 if op is None:
436 436 if transactiongetter is None:
437 437 transactiongetter = _notransaction
438 438 op = bundleoperation(repo, transactiongetter)
439 439 # todo:
440 440 # - replace this is a init function soon.
441 441 # - exception catching
442 442 unbundler.params
443 443 if repo.ui.debugflag:
444 444 msg = ['bundle2-input-bundle:']
445 445 if unbundler.params:
446 446 msg.append(' %i params' % len(unbundler.params))
447 447 if op._gettransaction is None or op._gettransaction is _notransaction:
448 448 msg.append(' no-transaction')
449 449 else:
450 450 msg.append(' with-transaction')
451 451 msg.append('\n')
452 452 repo.ui.debug(''.join(msg))
453 453
454 454 processparts(repo, op, unbundler)
455 455
456 456 return op
457 457
458 458 def processparts(repo, op, unbundler):
459 459 with partiterator(repo, op, unbundler) as parts:
460 460 for part in parts:
461 461 _processpart(op, part)
462 462
463 463 def _processchangegroup(op, cg, tr, source, url, **kwargs):
464 464 ret = cg.apply(op.repo, tr, source, url, **kwargs)
465 465 op.records.add('changegroup', {
466 466 'return': ret,
467 467 })
468 468 return ret
469 469
470 470 def _gethandler(op, part):
471 471 status = 'unknown' # used by debug output
472 472 try:
473 473 handler = parthandlermapping.get(part.type)
474 474 if handler is None:
475 475 status = 'unsupported-type'
476 476 raise error.BundleUnknownFeatureError(parttype=part.type)
477 477 indebug(op.ui, 'found a handler for part %s' % part.type)
478 478 unknownparams = part.mandatorykeys - handler.params
479 479 if unknownparams:
480 480 unknownparams = list(unknownparams)
481 481 unknownparams.sort()
482 482 status = 'unsupported-params (%s)' % ', '.join(unknownparams)
483 483 raise error.BundleUnknownFeatureError(parttype=part.type,
484 484 params=unknownparams)
485 485 status = 'supported'
486 486 except error.BundleUnknownFeatureError as exc:
487 487 if part.mandatory: # mandatory parts
488 488 raise
489 489 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
490 490 return # skip to part processing
491 491 finally:
492 492 if op.ui.debugflag:
493 493 msg = ['bundle2-input-part: "%s"' % part.type]
494 494 if not part.mandatory:
495 495 msg.append(' (advisory)')
496 496 nbmp = len(part.mandatorykeys)
497 497 nbap = len(part.params) - nbmp
498 498 if nbmp or nbap:
499 499 msg.append(' (params:')
500 500 if nbmp:
501 501 msg.append(' %i mandatory' % nbmp)
502 502 if nbap:
503 503 msg.append(' %i advisory' % nbmp)
504 504 msg.append(')')
505 505 msg.append(' %s\n' % status)
506 506 op.ui.debug(''.join(msg))
507 507
508 508 return handler
509 509
510 510 def _processpart(op, part):
511 511 """process a single part from a bundle
512 512
513 513 The part is guaranteed to have been fully consumed when the function exits
514 514 (even if an exception is raised)."""
515 515 handler = _gethandler(op, part)
516 516 if handler is None:
517 517 return
518 518
519 519 # handler is called outside the above try block so that we don't
520 520 # risk catching KeyErrors from anything other than the
521 521 # parthandlermapping lookup (any KeyError raised by handler()
522 522 # itself represents a defect of a different variety).
523 523 output = None
524 524 if op.captureoutput and op.reply is not None:
525 525 op.ui.pushbuffer(error=True, subproc=True)
526 526 output = ''
527 527 try:
528 528 handler(op, part)
529 529 finally:
530 530 if output is not None:
531 531 output = op.ui.popbuffer()
532 532 if output:
533 533 outpart = op.reply.newpart('output', data=output,
534 534 mandatory=False)
535 535 outpart.addparam(
536 536 'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
537 537
538 538 def decodecaps(blob):
539 539 """decode a bundle2 caps bytes blob into a dictionary
540 540
541 541 The blob is a list of capabilities (one per line)
542 542 Capabilities may have values using a line of the form::
543 543
544 544 capability=value1,value2,value3
545 545
546 546 The values are always a list."""
547 547 caps = {}
548 548 for line in blob.splitlines():
549 549 if not line:
550 550 continue
551 551 if '=' not in line:
552 552 key, vals = line, ()
553 553 else:
554 554 key, vals = line.split('=', 1)
555 555 vals = vals.split(',')
556 556 key = urlreq.unquote(key)
557 557 vals = [urlreq.unquote(v) for v in vals]
558 558 caps[key] = vals
559 559 return caps
560 560
561 561 def encodecaps(caps):
562 562 """encode a bundle2 caps dictionary into a bytes blob"""
563 563 chunks = []
564 564 for ca in sorted(caps):
565 565 vals = caps[ca]
566 566 ca = urlreq.quote(ca)
567 567 vals = [urlreq.quote(v) for v in vals]
568 568 if vals:
569 569 ca = "%s=%s" % (ca, ','.join(vals))
570 570 chunks.append(ca)
571 571 return '\n'.join(chunks)
572 572
573 573 bundletypes = {
574 574 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
575 575 # since the unification ssh accepts a header but there
576 576 # is no capability signaling it.
577 577 "HG20": (), # special-cased below
578 578 "HG10UN": ("HG10UN", 'UN'),
579 579 "HG10BZ": ("HG10", 'BZ'),
580 580 "HG10GZ": ("HG10GZ", 'GZ'),
581 581 }
582 582
583 583 # hgweb uses this list to communicate its preferred type
584 584 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
585 585
586 586 class bundle20(object):
587 587 """represent an outgoing bundle2 container
588 588
589 589 Use the `addparam` method to add stream level parameter. and `newpart` to
590 590 populate it. Then call `getchunks` to retrieve all the binary chunks of
591 591 data that compose the bundle2 container."""
592 592
593 593 _magicstring = 'HG20'
594 594
595 595 def __init__(self, ui, capabilities=()):
596 596 self.ui = ui
597 597 self._params = []
598 598 self._parts = []
599 599 self.capabilities = dict(capabilities)
600 600 self._compengine = util.compengines.forbundletype('UN')
601 601 self._compopts = None
602 602 # If compression is being handled by a consumer of the raw
603 603 # data (e.g. the wire protocol), unsetting this flag tells
604 604 # consumers that the bundle is best left uncompressed.
605 605 self.prefercompressed = True
606 606
607 607 def setcompression(self, alg, compopts=None):
608 608 """setup core part compression to <alg>"""
609 609 if alg in (None, 'UN'):
610 610 return
611 611 assert not any(n.lower() == 'compression' for n, v in self._params)
612 612 self.addparam('Compression', alg)
613 613 self._compengine = util.compengines.forbundletype(alg)
614 614 self._compopts = compopts
615 615
616 616 @property
617 617 def nbparts(self):
618 618 """total number of parts added to the bundler"""
619 619 return len(self._parts)
620 620
621 621 # methods used to defines the bundle2 content
622 622 def addparam(self, name, value=None):
623 623 """add a stream level parameter"""
624 624 if not name:
625 625 raise ValueError(r'empty parameter name')
626 626 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
627 627 raise ValueError(r'non letter first character: %s' % name)
628 628 self._params.append((name, value))
629 629
630 630 def addpart(self, part):
631 631 """add a new part to the bundle2 container
632 632
633 633 Parts contains the actual applicative payload."""
634 634 assert part.id is None
635 635 part.id = len(self._parts) # very cheap counter
636 636 self._parts.append(part)
637 637
638 638 def newpart(self, typeid, *args, **kwargs):
639 639 """create a new part and add it to the containers
640 640
641 641 As the part is directly added to the containers. For now, this means
642 642 that any failure to properly initialize the part after calling
643 643 ``newpart`` should result in a failure of the whole bundling process.
644 644
645 645 You can still fall back to manually create and add if you need better
646 646 control."""
647 647 part = bundlepart(typeid, *args, **kwargs)
648 648 self.addpart(part)
649 649 return part
650 650
651 651 # methods used to generate the bundle2 stream
652 652 def getchunks(self):
653 653 if self.ui.debugflag:
654 654 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
655 655 if self._params:
656 656 msg.append(' (%i params)' % len(self._params))
657 657 msg.append(' %i parts total\n' % len(self._parts))
658 658 self.ui.debug(''.join(msg))
659 659 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
660 660 yield self._magicstring
661 661 param = self._paramchunk()
662 662 outdebug(self.ui, 'bundle parameter: %s' % param)
663 663 yield _pack(_fstreamparamsize, len(param))
664 664 if param:
665 665 yield param
666 666 for chunk in self._compengine.compressstream(self._getcorechunk(),
667 667 self._compopts):
668 668 yield chunk
669 669
670 670 def _paramchunk(self):
671 671 """return a encoded version of all stream parameters"""
672 672 blocks = []
673 673 for par, value in self._params:
674 674 par = urlreq.quote(par)
675 675 if value is not None:
676 676 value = urlreq.quote(value)
677 677 par = '%s=%s' % (par, value)
678 678 blocks.append(par)
679 679 return ' '.join(blocks)
680 680
681 681 def _getcorechunk(self):
682 682 """yield chunk for the core part of the bundle
683 683
684 684 (all but headers and parameters)"""
685 685 outdebug(self.ui, 'start of parts')
686 686 for part in self._parts:
687 687 outdebug(self.ui, 'bundle part: "%s"' % part.type)
688 688 for chunk in part.getchunks(ui=self.ui):
689 689 yield chunk
690 690 outdebug(self.ui, 'end of bundle')
691 691 yield _pack(_fpartheadersize, 0)
692 692
693 693
694 694 def salvageoutput(self):
695 695 """return a list with a copy of all output parts in the bundle
696 696
697 697 This is meant to be used during error handling to make sure we preserve
698 698 server output"""
699 699 salvaged = []
700 700 for part in self._parts:
701 701 if part.type.startswith('output'):
702 702 salvaged.append(part.copy())
703 703 return salvaged
704 704
705 705
706 706 class unpackermixin(object):
707 707 """A mixin to extract bytes and struct data from a stream"""
708 708
709 709 def __init__(self, fp):
710 710 self._fp = fp
711 711
712 712 def _unpack(self, format):
713 713 """unpack this struct format from the stream
714 714
715 715 This method is meant for internal usage by the bundle2 protocol only.
716 716 They directly manipulate the low level stream including bundle2 level
717 717 instruction.
718 718
719 719 Do not use it to implement higher-level logic or methods."""
720 720 data = self._readexact(struct.calcsize(format))
721 721 return _unpack(format, data)
722 722
723 723 def _readexact(self, size):
724 724 """read exactly <size> bytes from the stream
725 725
726 726 This method is meant for internal usage by the bundle2 protocol only.
727 727 They directly manipulate the low level stream including bundle2 level
728 728 instruction.
729 729
730 730 Do not use it to implement higher-level logic or methods."""
731 731 return changegroup.readexactly(self._fp, size)
732 732
733 733 def getunbundler(ui, fp, magicstring=None):
734 734 """return a valid unbundler object for a given magicstring"""
735 735 if magicstring is None:
736 736 magicstring = changegroup.readexactly(fp, 4)
737 737 magic, version = magicstring[0:2], magicstring[2:4]
738 738 if magic != 'HG':
739 739 ui.debug(
740 740 "error: invalid magic: %r (version %r), should be 'HG'\n"
741 741 % (magic, version))
742 742 raise error.Abort(_('not a Mercurial bundle'))
743 743 unbundlerclass = formatmap.get(version)
744 744 if unbundlerclass is None:
745 745 raise error.Abort(_('unknown bundle version %s') % version)
746 746 unbundler = unbundlerclass(ui, fp)
747 747 indebug(ui, 'start processing of %s stream' % magicstring)
748 748 return unbundler
749 749
750 750 class unbundle20(unpackermixin):
751 751 """interpret a bundle2 stream
752 752
753 753 This class is fed with a binary stream and yields parts through its
754 754 `iterparts` methods."""
755 755
756 756 _magicstring = 'HG20'
757 757
758 758 def __init__(self, ui, fp):
759 759 """If header is specified, we do not read it out of the stream."""
760 760 self.ui = ui
761 761 self._compengine = util.compengines.forbundletype('UN')
762 762 self._compressed = None
763 763 super(unbundle20, self).__init__(fp)
764 764
765 765 @util.propertycache
766 766 def params(self):
767 767 """dictionary of stream level parameters"""
768 768 indebug(self.ui, 'reading bundle2 stream parameters')
769 769 params = {}
770 770 paramssize = self._unpack(_fstreamparamsize)[0]
771 771 if paramssize < 0:
772 772 raise error.BundleValueError('negative bundle param size: %i'
773 773 % paramssize)
774 774 if paramssize:
775 775 params = self._readexact(paramssize)
776 776 params = self._processallparams(params)
777 777 return params
778 778
779 779 def _processallparams(self, paramsblock):
780 780 """"""
781 781 params = util.sortdict()
782 782 for p in paramsblock.split(' '):
783 783 p = p.split('=', 1)
784 784 p = [urlreq.unquote(i) for i in p]
785 785 if len(p) < 2:
786 786 p.append(None)
787 787 self._processparam(*p)
788 788 params[p[0]] = p[1]
789 789 return params
790 790
791 791
792 792 def _processparam(self, name, value):
793 793 """process a parameter, applying its effect if needed
794 794
795 795 Parameter starting with a lower case letter are advisory and will be
796 796 ignored when unknown. Those starting with an upper case letter are
797 797 mandatory and will this function will raise a KeyError when unknown.
798 798
799 799 Note: no option are currently supported. Any input will be either
800 800 ignored or failing.
801 801 """
802 802 if not name:
803 803 raise ValueError(r'empty parameter name')
804 804 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
805 805 raise ValueError(r'non letter first character: %s' % name)
806 806 try:
807 807 handler = b2streamparamsmap[name.lower()]
808 808 except KeyError:
809 809 if name[0:1].islower():
810 810 indebug(self.ui, "ignoring unknown parameter %s" % name)
811 811 else:
812 812 raise error.BundleUnknownFeatureError(params=(name,))
813 813 else:
814 814 handler(self, name, value)
815 815
816 816 def _forwardchunks(self):
817 817 """utility to transfer a bundle2 as binary
818 818
819 819 This is made necessary by the fact the 'getbundle' command over 'ssh'
820 820 have no way to know then the reply end, relying on the bundle to be
821 821 interpreted to know its end. This is terrible and we are sorry, but we
822 822 needed to move forward to get general delta enabled.
823 823 """
824 824 yield self._magicstring
825 825 assert 'params' not in vars(self)
826 826 paramssize = self._unpack(_fstreamparamsize)[0]
827 827 if paramssize < 0:
828 828 raise error.BundleValueError('negative bundle param size: %i'
829 829 % paramssize)
830 830 yield _pack(_fstreamparamsize, paramssize)
831 831 if paramssize:
832 832 params = self._readexact(paramssize)
833 833 self._processallparams(params)
834 834 yield params
835 835 assert self._compengine.bundletype == 'UN'
836 836 # From there, payload might need to be decompressed
837 837 self._fp = self._compengine.decompressorreader(self._fp)
838 838 emptycount = 0
839 839 while emptycount < 2:
840 840 # so we can brainlessly loop
841 841 assert _fpartheadersize == _fpayloadsize
842 842 size = self._unpack(_fpartheadersize)[0]
843 843 yield _pack(_fpartheadersize, size)
844 844 if size:
845 845 emptycount = 0
846 846 else:
847 847 emptycount += 1
848 848 continue
849 849 if size == flaginterrupt:
850 850 continue
851 851 elif size < 0:
852 852 raise error.BundleValueError('negative chunk size: %i')
853 853 yield self._readexact(size)
854 854
855 855
856 856 def iterparts(self, seekable=False):
857 857 """yield all parts contained in the stream"""
858 858 cls = seekableunbundlepart if seekable else unbundlepart
859 859 # make sure param have been loaded
860 860 self.params
861 861 # From there, payload need to be decompressed
862 862 self._fp = self._compengine.decompressorreader(self._fp)
863 863 indebug(self.ui, 'start extraction of bundle2 parts')
864 864 headerblock = self._readpartheader()
865 865 while headerblock is not None:
866 866 part = cls(self.ui, headerblock, self._fp)
867 867 yield part
868 868 # Ensure part is fully consumed so we can start reading the next
869 869 # part.
870 870 part.consume()
871 871
872 872 headerblock = self._readpartheader()
873 873 indebug(self.ui, 'end of bundle2 stream')
874 874
875 875 def _readpartheader(self):
876 876 """reads a part header size and return the bytes blob
877 877
878 878 returns None if empty"""
879 879 headersize = self._unpack(_fpartheadersize)[0]
880 880 if headersize < 0:
881 881 raise error.BundleValueError('negative part header size: %i'
882 882 % headersize)
883 883 indebug(self.ui, 'part header size: %i' % headersize)
884 884 if headersize:
885 885 return self._readexact(headersize)
886 886 return None
887 887
888 888 def compressed(self):
889 889 self.params # load params
890 890 return self._compressed
891 891
892 892 def close(self):
893 893 """close underlying file"""
894 894 if util.safehasattr(self._fp, 'close'):
895 895 return self._fp.close()
896 896
897 897 formatmap = {'20': unbundle20}
898 898
899 899 b2streamparamsmap = {}
900 900
901 901 def b2streamparamhandler(name):
902 902 """register a handler for a stream level parameter"""
903 903 def decorator(func):
904 904 assert name not in formatmap
905 905 b2streamparamsmap[name] = func
906 906 return func
907 907 return decorator
908 908
909 909 @b2streamparamhandler('compression')
910 910 def processcompression(unbundler, param, value):
911 911 """read compression parameter and install payload decompression"""
912 912 if value not in util.compengines.supportedbundletypes:
913 913 raise error.BundleUnknownFeatureError(params=(param,),
914 914 values=(value,))
915 915 unbundler._compengine = util.compengines.forbundletype(value)
916 916 if value is not None:
917 917 unbundler._compressed = True
918 918
919 919 class bundlepart(object):
920 920 """A bundle2 part contains application level payload
921 921
922 922 The part `type` is used to route the part to the application level
923 923 handler.
924 924
925 925 The part payload is contained in ``part.data``. It could be raw bytes or a
926 926 generator of byte chunks.
927 927
928 928 You can add parameters to the part using the ``addparam`` method.
929 929 Parameters can be either mandatory (default) or advisory. Remote side
930 930 should be able to safely ignore the advisory ones.
931 931
932 932 Both data and parameters cannot be modified after the generation has begun.
933 933 """
934 934
935 935 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
936 936 data='', mandatory=True):
937 937 validateparttype(parttype)
938 938 self.id = None
939 939 self.type = parttype
940 940 self._data = data
941 941 self._mandatoryparams = list(mandatoryparams)
942 942 self._advisoryparams = list(advisoryparams)
943 943 # checking for duplicated entries
944 944 self._seenparams = set()
945 945 for pname, __ in self._mandatoryparams + self._advisoryparams:
946 946 if pname in self._seenparams:
947 947 raise error.ProgrammingError('duplicated params: %s' % pname)
948 948 self._seenparams.add(pname)
949 949 # status of the part's generation:
950 950 # - None: not started,
951 951 # - False: currently generated,
952 952 # - True: generation done.
953 953 self._generated = None
954 954 self.mandatory = mandatory
955 955
956 956 def __repr__(self):
957 957 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
958 958 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
959 959 % (cls, id(self), self.id, self.type, self.mandatory))
960 960
961 961 def copy(self):
962 962 """return a copy of the part
963 963
964 964 The new part have the very same content but no partid assigned yet.
965 965 Parts with generated data cannot be copied."""
966 966 assert not util.safehasattr(self.data, 'next')
967 967 return self.__class__(self.type, self._mandatoryparams,
968 968 self._advisoryparams, self._data, self.mandatory)
969 969
970 970 # methods used to defines the part content
971 971 @property
972 972 def data(self):
973 973 return self._data
974 974
975 975 @data.setter
976 976 def data(self, data):
977 977 if self._generated is not None:
978 978 raise error.ReadOnlyPartError('part is being generated')
979 979 self._data = data
980 980
981 981 @property
982 982 def mandatoryparams(self):
983 983 # make it an immutable tuple to force people through ``addparam``
984 984 return tuple(self._mandatoryparams)
985 985
986 986 @property
987 987 def advisoryparams(self):
988 988 # make it an immutable tuple to force people through ``addparam``
989 989 return tuple(self._advisoryparams)
990 990
991 991 def addparam(self, name, value='', mandatory=True):
992 992 """add a parameter to the part
993 993
994 994 If 'mandatory' is set to True, the remote handler must claim support
995 995 for this parameter or the unbundling will be aborted.
996 996
997 997 The 'name' and 'value' cannot exceed 255 bytes each.
998 998 """
999 999 if self._generated is not None:
1000 1000 raise error.ReadOnlyPartError('part is being generated')
1001 1001 if name in self._seenparams:
1002 1002 raise ValueError('duplicated params: %s' % name)
1003 1003 self._seenparams.add(name)
1004 1004 params = self._advisoryparams
1005 1005 if mandatory:
1006 1006 params = self._mandatoryparams
1007 1007 params.append((name, value))
1008 1008
1009 1009 # methods used to generates the bundle2 stream
1010 1010 def getchunks(self, ui):
1011 1011 if self._generated is not None:
1012 1012 raise error.ProgrammingError('part can only be consumed once')
1013 1013 self._generated = False
1014 1014
1015 1015 if ui.debugflag:
1016 1016 msg = ['bundle2-output-part: "%s"' % self.type]
1017 1017 if not self.mandatory:
1018 1018 msg.append(' (advisory)')
1019 1019 nbmp = len(self.mandatoryparams)
1020 1020 nbap = len(self.advisoryparams)
1021 1021 if nbmp or nbap:
1022 1022 msg.append(' (params:')
1023 1023 if nbmp:
1024 1024 msg.append(' %i mandatory' % nbmp)
1025 1025 if nbap:
1026 1026 msg.append(' %i advisory' % nbmp)
1027 1027 msg.append(')')
1028 1028 if not self.data:
1029 1029 msg.append(' empty payload')
1030 1030 elif (util.safehasattr(self.data, 'next')
1031 1031 or util.safehasattr(self.data, '__next__')):
1032 1032 msg.append(' streamed payload')
1033 1033 else:
1034 1034 msg.append(' %i bytes payload' % len(self.data))
1035 1035 msg.append('\n')
1036 1036 ui.debug(''.join(msg))
1037 1037
1038 1038 #### header
1039 1039 if self.mandatory:
1040 1040 parttype = self.type.upper()
1041 1041 else:
1042 1042 parttype = self.type.lower()
1043 1043 outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
1044 1044 ## parttype
1045 1045 header = [_pack(_fparttypesize, len(parttype)),
1046 1046 parttype, _pack(_fpartid, self.id),
1047 1047 ]
1048 1048 ## parameters
1049 1049 # count
1050 1050 manpar = self.mandatoryparams
1051 1051 advpar = self.advisoryparams
1052 1052 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
1053 1053 # size
1054 1054 parsizes = []
1055 1055 for key, value in manpar:
1056 1056 parsizes.append(len(key))
1057 1057 parsizes.append(len(value))
1058 1058 for key, value in advpar:
1059 1059 parsizes.append(len(key))
1060 1060 parsizes.append(len(value))
1061 1061 paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
1062 1062 header.append(paramsizes)
1063 1063 # key, value
1064 1064 for key, value in manpar:
1065 1065 header.append(key)
1066 1066 header.append(value)
1067 1067 for key, value in advpar:
1068 1068 header.append(key)
1069 1069 header.append(value)
1070 1070 ## finalize header
1071 1071 try:
1072 1072 headerchunk = ''.join(header)
1073 1073 except TypeError:
1074 1074 raise TypeError(r'Found a non-bytes trying to '
1075 1075 r'build bundle part header: %r' % header)
1076 1076 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1077 1077 yield _pack(_fpartheadersize, len(headerchunk))
1078 1078 yield headerchunk
1079 1079 ## payload
1080 1080 try:
1081 1081 for chunk in self._payloadchunks():
1082 1082 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1083 1083 yield _pack(_fpayloadsize, len(chunk))
1084 1084 yield chunk
1085 1085 except GeneratorExit:
1086 1086 # GeneratorExit means that nobody is listening for our
1087 1087 # results anyway, so just bail quickly rather than trying
1088 1088 # to produce an error part.
1089 1089 ui.debug('bundle2-generatorexit\n')
1090 1090 raise
1091 1091 except BaseException as exc:
1092 1092 bexc = util.forcebytestr(exc)
1093 1093 # backup exception data for later
1094 1094 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1095 1095 % bexc)
1096 1096 tb = sys.exc_info()[2]
1097 1097 msg = 'unexpected error: %s' % bexc
1098 1098 interpart = bundlepart('error:abort', [('message', msg)],
1099 1099 mandatory=False)
1100 1100 interpart.id = 0
1101 1101 yield _pack(_fpayloadsize, -1)
1102 1102 for chunk in interpart.getchunks(ui=ui):
1103 1103 yield chunk
1104 1104 outdebug(ui, 'closing payload chunk')
1105 1105 # abort current part payload
1106 1106 yield _pack(_fpayloadsize, 0)
1107 1107 pycompat.raisewithtb(exc, tb)
1108 1108 # end of payload
1109 1109 outdebug(ui, 'closing payload chunk')
1110 1110 yield _pack(_fpayloadsize, 0)
1111 1111 self._generated = True
1112 1112
1113 1113 def _payloadchunks(self):
1114 1114 """yield chunks of a the part payload
1115 1115
1116 1116 Exists to handle the different methods to provide data to a part."""
1117 1117 # we only support fixed size data now.
1118 1118 # This will be improved in the future.
1119 1119 if (util.safehasattr(self.data, 'next')
1120 1120 or util.safehasattr(self.data, '__next__')):
1121 1121 buff = util.chunkbuffer(self.data)
1122 1122 chunk = buff.read(preferedchunksize)
1123 1123 while chunk:
1124 1124 yield chunk
1125 1125 chunk = buff.read(preferedchunksize)
1126 1126 elif len(self.data):
1127 1127 yield self.data
1128 1128
1129 1129
1130 1130 flaginterrupt = -1
1131 1131
1132 1132 class interrupthandler(unpackermixin):
1133 1133 """read one part and process it with restricted capability
1134 1134
1135 1135 This allows to transmit exception raised on the producer size during part
1136 1136 iteration while the consumer is reading a part.
1137 1137
1138 1138 Part processed in this manner only have access to a ui object,"""
1139 1139
1140 1140 def __init__(self, ui, fp):
1141 1141 super(interrupthandler, self).__init__(fp)
1142 1142 self.ui = ui
1143 1143
1144 1144 def _readpartheader(self):
1145 1145 """reads a part header size and return the bytes blob
1146 1146
1147 1147 returns None if empty"""
1148 1148 headersize = self._unpack(_fpartheadersize)[0]
1149 1149 if headersize < 0:
1150 1150 raise error.BundleValueError('negative part header size: %i'
1151 1151 % headersize)
1152 1152 indebug(self.ui, 'part header size: %i\n' % headersize)
1153 1153 if headersize:
1154 1154 return self._readexact(headersize)
1155 1155 return None
1156 1156
1157 1157 def __call__(self):
1158 1158
1159 1159 self.ui.debug('bundle2-input-stream-interrupt:'
1160 1160 ' opening out of band context\n')
1161 1161 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1162 1162 headerblock = self._readpartheader()
1163 1163 if headerblock is None:
1164 1164 indebug(self.ui, 'no part found during interruption.')
1165 1165 return
1166 1166 part = unbundlepart(self.ui, headerblock, self._fp)
1167 1167 op = interruptoperation(self.ui)
1168 1168 hardabort = False
1169 1169 try:
1170 1170 _processpart(op, part)
1171 1171 except (SystemExit, KeyboardInterrupt):
1172 1172 hardabort = True
1173 1173 raise
1174 1174 finally:
1175 1175 if not hardabort:
1176 1176 part.consume()
1177 1177 self.ui.debug('bundle2-input-stream-interrupt:'
1178 1178 ' closing out of band context\n')
1179 1179
1180 1180 class interruptoperation(object):
1181 1181 """A limited operation to be use by part handler during interruption
1182 1182
1183 1183 It only have access to an ui object.
1184 1184 """
1185 1185
1186 1186 def __init__(self, ui):
1187 1187 self.ui = ui
1188 1188 self.reply = None
1189 1189 self.captureoutput = False
1190 1190
1191 1191 @property
1192 1192 def repo(self):
1193 1193 raise error.ProgrammingError('no repo access from stream interruption')
1194 1194
1195 1195 def gettransaction(self):
1196 1196 raise TransactionUnavailable('no repo access from stream interruption')
1197 1197
1198 1198 def decodepayloadchunks(ui, fh):
1199 1199 """Reads bundle2 part payload data into chunks.
1200 1200
1201 1201 Part payload data consists of framed chunks. This function takes
1202 1202 a file handle and emits those chunks.
1203 1203 """
1204 1204 dolog = ui.configbool('devel', 'bundle2.debug')
1205 1205 debug = ui.debug
1206 1206
1207 1207 headerstruct = struct.Struct(_fpayloadsize)
1208 1208 headersize = headerstruct.size
1209 1209 unpack = headerstruct.unpack
1210 1210
1211 1211 readexactly = changegroup.readexactly
1212 1212 read = fh.read
1213 1213
1214 1214 chunksize = unpack(readexactly(fh, headersize))[0]
1215 1215 indebug(ui, 'payload chunk size: %i' % chunksize)
1216 1216
1217 1217 # changegroup.readexactly() is inlined below for performance.
1218 1218 while chunksize:
1219 1219 if chunksize >= 0:
1220 1220 s = read(chunksize)
1221 1221 if len(s) < chunksize:
1222 1222 raise error.Abort(_('stream ended unexpectedly '
1223 1223 ' (got %d bytes, expected %d)') %
1224 1224 (len(s), chunksize))
1225 1225
1226 1226 yield s
1227 1227 elif chunksize == flaginterrupt:
1228 1228 # Interrupt "signal" detected. The regular stream is interrupted
1229 1229 # and a bundle2 part follows. Consume it.
1230 1230 interrupthandler(ui, fh)()
1231 1231 else:
1232 1232 raise error.BundleValueError(
1233 1233 'negative payload chunk size: %s' % chunksize)
1234 1234
1235 1235 s = read(headersize)
1236 1236 if len(s) < headersize:
1237 1237 raise error.Abort(_('stream ended unexpectedly '
1238 1238 ' (got %d bytes, expected %d)') %
1239 1239 (len(s), chunksize))
1240 1240
1241 1241 chunksize = unpack(s)[0]
1242 1242
1243 1243 # indebug() inlined for performance.
1244 1244 if dolog:
1245 1245 debug('bundle2-input: payload chunk size: %i\n' % chunksize)
1246 1246
1247 1247 class unbundlepart(unpackermixin):
1248 1248 """a bundle part read from a bundle"""
1249 1249
1250 1250 def __init__(self, ui, header, fp):
1251 1251 super(unbundlepart, self).__init__(fp)
1252 1252 self._seekable = (util.safehasattr(fp, 'seek') and
1253 1253 util.safehasattr(fp, 'tell'))
1254 1254 self.ui = ui
1255 1255 # unbundle state attr
1256 1256 self._headerdata = header
1257 1257 self._headeroffset = 0
1258 1258 self._initialized = False
1259 1259 self.consumed = False
1260 1260 # part data
1261 1261 self.id = None
1262 1262 self.type = None
1263 1263 self.mandatoryparams = None
1264 1264 self.advisoryparams = None
1265 1265 self.params = None
1266 1266 self.mandatorykeys = ()
1267 1267 self._readheader()
1268 1268 self._mandatory = None
1269 1269 self._pos = 0
1270 1270
1271 1271 def _fromheader(self, size):
1272 1272 """return the next <size> byte from the header"""
1273 1273 offset = self._headeroffset
1274 1274 data = self._headerdata[offset:(offset + size)]
1275 1275 self._headeroffset = offset + size
1276 1276 return data
1277 1277
1278 1278 def _unpackheader(self, format):
1279 1279 """read given format from header
1280 1280
1281 1281 This automatically compute the size of the format to read."""
1282 1282 data = self._fromheader(struct.calcsize(format))
1283 1283 return _unpack(format, data)
1284 1284
1285 1285 def _initparams(self, mandatoryparams, advisoryparams):
1286 1286 """internal function to setup all logic related parameters"""
1287 1287 # make it read only to prevent people touching it by mistake.
1288 1288 self.mandatoryparams = tuple(mandatoryparams)
1289 1289 self.advisoryparams = tuple(advisoryparams)
1290 1290 # user friendly UI
1291 1291 self.params = util.sortdict(self.mandatoryparams)
1292 1292 self.params.update(self.advisoryparams)
1293 1293 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1294 1294
1295 1295 def _readheader(self):
1296 1296 """read the header and setup the object"""
1297 1297 typesize = self._unpackheader(_fparttypesize)[0]
1298 1298 self.type = self._fromheader(typesize)
1299 1299 indebug(self.ui, 'part type: "%s"' % self.type)
1300 1300 self.id = self._unpackheader(_fpartid)[0]
1301 1301 indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
1302 1302 # extract mandatory bit from type
1303 1303 self.mandatory = (self.type != self.type.lower())
1304 1304 self.type = self.type.lower()
1305 1305 ## reading parameters
1306 1306 # param count
1307 1307 mancount, advcount = self._unpackheader(_fpartparamcount)
1308 1308 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1309 1309 # param size
1310 1310 fparamsizes = _makefpartparamsizes(mancount + advcount)
1311 1311 paramsizes = self._unpackheader(fparamsizes)
1312 1312 # make it a list of couple again
1313 1313 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
1314 1314 # split mandatory from advisory
1315 1315 mansizes = paramsizes[:mancount]
1316 1316 advsizes = paramsizes[mancount:]
1317 1317 # retrieve param value
1318 1318 manparams = []
1319 1319 for key, value in mansizes:
1320 1320 manparams.append((self._fromheader(key), self._fromheader(value)))
1321 1321 advparams = []
1322 1322 for key, value in advsizes:
1323 1323 advparams.append((self._fromheader(key), self._fromheader(value)))
1324 1324 self._initparams(manparams, advparams)
1325 1325 ## part payload
1326 1326 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1327 1327 # we read the data, tell it
1328 1328 self._initialized = True
1329 1329
1330 1330 def _payloadchunks(self):
1331 1331 """Generator of decoded chunks in the payload."""
1332 1332 return decodepayloadchunks(self.ui, self._fp)
1333 1333
1334 1334 def consume(self):
1335 1335 """Read the part payload until completion.
1336 1336
1337 1337 By consuming the part data, the underlying stream read offset will
1338 1338 be advanced to the next part (or end of stream).
1339 1339 """
1340 1340 if self.consumed:
1341 1341 return
1342 1342
1343 1343 chunk = self.read(32768)
1344 1344 while chunk:
1345 1345 self._pos += len(chunk)
1346 1346 chunk = self.read(32768)
1347 1347
1348 1348 def read(self, size=None):
1349 1349 """read payload data"""
1350 1350 if not self._initialized:
1351 1351 self._readheader()
1352 1352 if size is None:
1353 1353 data = self._payloadstream.read()
1354 1354 else:
1355 1355 data = self._payloadstream.read(size)
1356 1356 self._pos += len(data)
1357 1357 if size is None or len(data) < size:
1358 1358 if not self.consumed and self._pos:
1359 1359 self.ui.debug('bundle2-input-part: total payload size %i\n'
1360 1360 % self._pos)
1361 1361 self.consumed = True
1362 1362 return data
1363 1363
1364 1364 class seekableunbundlepart(unbundlepart):
1365 1365 """A bundle2 part in a bundle that is seekable.
1366 1366
1367 1367 Regular ``unbundlepart`` instances can only be read once. This class
1368 1368 extends ``unbundlepart`` to enable bi-directional seeking within the
1369 1369 part.
1370 1370
1371 1371 Bundle2 part data consists of framed chunks. Offsets when seeking
1372 1372 refer to the decoded data, not the offsets in the underlying bundle2
1373 1373 stream.
1374 1374
1375 1375 To facilitate quickly seeking within the decoded data, instances of this
1376 1376 class maintain a mapping between offsets in the underlying stream and
1377 1377 the decoded payload. This mapping will consume memory in proportion
1378 1378 to the number of chunks within the payload (which almost certainly
1379 1379 increases in proportion with the size of the part).
1380 1380 """
1381 1381 def __init__(self, ui, header, fp):
1382 1382 # (payload, file) offsets for chunk starts.
1383 1383 self._chunkindex = []
1384 1384
1385 1385 super(seekableunbundlepart, self).__init__(ui, header, fp)
1386 1386
1387 1387 def _payloadchunks(self, chunknum=0):
1388 1388 '''seek to specified chunk and start yielding data'''
1389 1389 if len(self._chunkindex) == 0:
1390 1390 assert chunknum == 0, 'Must start with chunk 0'
1391 1391 self._chunkindex.append((0, self._tellfp()))
1392 1392 else:
1393 1393 assert chunknum < len(self._chunkindex), \
1394 1394 'Unknown chunk %d' % chunknum
1395 1395 self._seekfp(self._chunkindex[chunknum][1])
1396 1396
1397 1397 pos = self._chunkindex[chunknum][0]
1398 1398
1399 1399 for chunk in decodepayloadchunks(self.ui, self._fp):
1400 1400 chunknum += 1
1401 1401 pos += len(chunk)
1402 1402 if chunknum == len(self._chunkindex):
1403 1403 self._chunkindex.append((pos, self._tellfp()))
1404 1404
1405 1405 yield chunk
1406 1406
1407 1407 def _findchunk(self, pos):
1408 1408 '''for a given payload position, return a chunk number and offset'''
1409 1409 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1410 1410 if ppos == pos:
1411 1411 return chunk, 0
1412 1412 elif ppos > pos:
1413 1413 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1414 1414 raise ValueError('Unknown chunk')
1415 1415
1416 1416 def tell(self):
1417 1417 return self._pos
1418 1418
1419 1419 def seek(self, offset, whence=os.SEEK_SET):
1420 1420 if whence == os.SEEK_SET:
1421 1421 newpos = offset
1422 1422 elif whence == os.SEEK_CUR:
1423 1423 newpos = self._pos + offset
1424 1424 elif whence == os.SEEK_END:
1425 1425 if not self.consumed:
1426 1426 # Can't use self.consume() here because it advances self._pos.
1427 1427 chunk = self.read(32768)
1428 1428 while chunk:
1429 1429 chunk = self.read(32768)
1430 1430 newpos = self._chunkindex[-1][0] - offset
1431 1431 else:
1432 1432 raise ValueError('Unknown whence value: %r' % (whence,))
1433 1433
1434 1434 if newpos > self._chunkindex[-1][0] and not self.consumed:
1435 1435 # Can't use self.consume() here because it advances self._pos.
1436 1436 chunk = self.read(32768)
1437 1437 while chunk:
1438 1438 chunk = self.read(32668)
1439 1439
1440 1440 if not 0 <= newpos <= self._chunkindex[-1][0]:
1441 1441 raise ValueError('Offset out of range')
1442 1442
1443 1443 if self._pos != newpos:
1444 1444 chunk, internaloffset = self._findchunk(newpos)
1445 1445 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1446 1446 adjust = self.read(internaloffset)
1447 1447 if len(adjust) != internaloffset:
1448 1448 raise error.Abort(_('Seek failed\n'))
1449 1449 self._pos = newpos
1450 1450
1451 1451 def _seekfp(self, offset, whence=0):
1452 1452 """move the underlying file pointer
1453 1453
1454 1454 This method is meant for internal usage by the bundle2 protocol only.
1455 1455 They directly manipulate the low level stream including bundle2 level
1456 1456 instruction.
1457 1457
1458 1458 Do not use it to implement higher-level logic or methods."""
1459 1459 if self._seekable:
1460 1460 return self._fp.seek(offset, whence)
1461 1461 else:
1462 1462 raise NotImplementedError(_('File pointer is not seekable'))
1463 1463
1464 1464 def _tellfp(self):
1465 1465 """return the file offset, or None if file is not seekable
1466 1466
1467 1467 This method is meant for internal usage by the bundle2 protocol only.
1468 1468 They directly manipulate the low level stream including bundle2 level
1469 1469 instruction.
1470 1470
1471 1471 Do not use it to implement higher-level logic or methods."""
1472 1472 if self._seekable:
1473 1473 try:
1474 1474 return self._fp.tell()
1475 1475 except IOError as e:
1476 1476 if e.errno == errno.ESPIPE:
1477 1477 self._seekable = False
1478 1478 else:
1479 1479 raise
1480 1480 return None
1481 1481
1482 1482 # These are only the static capabilities.
1483 1483 # Check the 'getrepocaps' function for the rest.
1484 1484 capabilities = {'HG20': (),
1485 1485 'bookmarks': (),
1486 1486 'error': ('abort', 'unsupportedcontent', 'pushraced',
1487 1487 'pushkey'),
1488 1488 'listkeys': (),
1489 1489 'pushkey': (),
1490 1490 'digests': tuple(sorted(util.DIGESTS.keys())),
1491 1491 'remote-changegroup': ('http', 'https'),
1492 1492 'hgtagsfnodes': (),
1493 1493 'phases': ('heads',),
1494 1494 'stream': ('v2',),
1495 1495 }
1496 1496
1497 1497 def getrepocaps(repo, allowpushback=False, role=None):
1498 1498 """return the bundle2 capabilities for a given repo
1499 1499
1500 1500 Exists to allow extensions (like evolution) to mutate the capabilities.
1501 1501
1502 1502 The returned value is used for servers advertising their capabilities as
1503 1503 well as clients advertising their capabilities to servers as part of
1504 1504 bundle2 requests. The ``role`` argument specifies which is which.
1505 1505 """
1506 1506 if role not in ('client', 'server'):
1507 1507 raise error.ProgrammingError('role argument must be client or server')
1508 1508
1509 1509 caps = capabilities.copy()
1510 1510 caps['changegroup'] = tuple(sorted(
1511 1511 changegroup.supportedincomingversions(repo)))
1512 1512 if obsolete.isenabled(repo, obsolete.exchangeopt):
1513 1513 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1514 1514 caps['obsmarkers'] = supportedformat
1515 1515 if allowpushback:
1516 1516 caps['pushback'] = ()
1517 1517 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1518 1518 if cpmode == 'check-related':
1519 1519 caps['checkheads'] = ('related',)
1520 1520 if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
1521 1521 caps.pop('phases')
1522 1522
1523 1523 # Don't advertise stream clone support in server mode if not configured.
1524 1524 if role == 'server':
1525 1525 streamsupported = repo.ui.configbool('server', 'uncompressed',
1526 1526 untrusted=True)
1527 1527 featuresupported = repo.ui.configbool('experimental', 'bundle2.stream')
1528 1528
1529 1529 if not streamsupported or not featuresupported:
1530 1530 caps.pop('stream')
1531 1531 # Else always advertise support on client, because payload support
1532 1532 # should always be advertised.
1533 1533
1534 1534 return caps
1535 1535
1536 1536 def bundle2caps(remote):
1537 1537 """return the bundle capabilities of a peer as dict"""
1538 1538 raw = remote.capable('bundle2')
1539 1539 if not raw and raw != '':
1540 1540 return {}
1541 1541 capsblob = urlreq.unquote(remote.capable('bundle2'))
1542 1542 return decodecaps(capsblob)
1543 1543
1544 1544 def obsmarkersversion(caps):
1545 1545 """extract the list of supported obsmarkers versions from a bundle2caps dict
1546 1546 """
1547 1547 obscaps = caps.get('obsmarkers', ())
1548 1548 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1549 1549
1550 1550 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1551 1551 vfs=None, compression=None, compopts=None):
1552 1552 if bundletype.startswith('HG10'):
1553 1553 cg = changegroup.makechangegroup(repo, outgoing, '01', source)
1554 1554 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1555 1555 compression=compression, compopts=compopts)
1556 1556 elif not bundletype.startswith('HG20'):
1557 1557 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1558 1558
1559 1559 caps = {}
1560 1560 if 'obsolescence' in opts:
1561 1561 caps['obsmarkers'] = ('V1',)
1562 1562 bundle = bundle20(ui, caps)
1563 1563 bundle.setcompression(compression, compopts)
1564 1564 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1565 1565 chunkiter = bundle.getchunks()
1566 1566
1567 1567 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1568 1568
1569 1569 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1570 1570 # We should eventually reconcile this logic with the one behind
1571 1571 # 'exchange.getbundle2partsgenerator'.
1572 1572 #
1573 1573 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1574 1574 # different right now. So we keep them separated for now for the sake of
1575 1575 # simplicity.
1576 1576
1577 1577 # we always want a changegroup in such bundle
1578 1578 cgversion = opts.get('cg.version')
1579 1579 if cgversion is None:
1580 1580 cgversion = changegroup.safeversion(repo)
1581 1581 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
1582 1582 part = bundler.newpart('changegroup', data=cg.getchunks())
1583 1583 part.addparam('version', cg.version)
1584 1584 if 'clcount' in cg.extras:
1585 1585 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1586 1586 mandatory=False)
1587 1587 if opts.get('phases') and repo.revs('%ln and secret()',
1588 1588 outgoing.missingheads):
1589 1589 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1590 1590
1591 1591 addparttagsfnodescache(repo, bundler, outgoing)
1592 1592
1593 1593 if opts.get('obsolescence', False):
1594 1594 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1595 1595 buildobsmarkerspart(bundler, obsmarkers)
1596 1596
1597 1597 if opts.get('phases', False):
1598 1598 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1599 1599 phasedata = phases.binaryencode(headsbyphase)
1600 1600 bundler.newpart('phase-heads', data=phasedata)
1601 1601
1602 1602 def addparttagsfnodescache(repo, bundler, outgoing):
1603 1603 # we include the tags fnode cache for the bundle changeset
1604 1604 # (as an optional parts)
1605 1605 cache = tags.hgtagsfnodescache(repo.unfiltered())
1606 1606 chunks = []
1607 1607
1608 1608 # .hgtags fnodes are only relevant for head changesets. While we could
1609 1609 # transfer values for all known nodes, there will likely be little to
1610 1610 # no benefit.
1611 1611 #
1612 1612 # We don't bother using a generator to produce output data because
1613 1613 # a) we only have 40 bytes per head and even esoteric numbers of heads
1614 1614 # consume little memory (1M heads is 40MB) b) we don't want to send the
1615 1615 # part if we don't have entries and knowing if we have entries requires
1616 1616 # cache lookups.
1617 1617 for node in outgoing.missingheads:
1618 1618 # Don't compute missing, as this may slow down serving.
1619 1619 fnode = cache.getfnode(node, computemissing=False)
1620 1620 if fnode is not None:
1621 1621 chunks.extend([node, fnode])
1622 1622
1623 1623 if chunks:
1624 1624 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1625 1625
1626 1626 def buildobsmarkerspart(bundler, markers):
1627 1627 """add an obsmarker part to the bundler with <markers>
1628 1628
1629 1629 No part is created if markers is empty.
1630 1630 Raises ValueError if the bundler doesn't support any known obsmarker format.
1631 1631 """
1632 1632 if not markers:
1633 1633 return None
1634 1634
1635 1635 remoteversions = obsmarkersversion(bundler.capabilities)
1636 1636 version = obsolete.commonversion(remoteversions)
1637 1637 if version is None:
1638 1638 raise ValueError('bundler does not support common obsmarker format')
1639 1639 stream = obsolete.encodemarkers(markers, True, version=version)
1640 1640 return bundler.newpart('obsmarkers', data=stream)
1641 1641
1642 1642 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1643 1643 compopts=None):
1644 1644 """Write a bundle file and return its filename.
1645 1645
1646 1646 Existing files will not be overwritten.
1647 1647 If no filename is specified, a temporary file is created.
1648 1648 bz2 compression can be turned off.
1649 1649 The bundle file will be deleted in case of errors.
1650 1650 """
1651 1651
1652 1652 if bundletype == "HG20":
1653 1653 bundle = bundle20(ui)
1654 1654 bundle.setcompression(compression, compopts)
1655 1655 part = bundle.newpart('changegroup', data=cg.getchunks())
1656 1656 part.addparam('version', cg.version)
1657 1657 if 'clcount' in cg.extras:
1658 1658 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1659 1659 mandatory=False)
1660 1660 chunkiter = bundle.getchunks()
1661 1661 else:
1662 1662 # compression argument is only for the bundle2 case
1663 1663 assert compression is None
1664 1664 if cg.version != '01':
1665 1665 raise error.Abort(_('old bundle types only supports v1 '
1666 1666 'changegroups'))
1667 1667 header, comp = bundletypes[bundletype]
1668 1668 if comp not in util.compengines.supportedbundletypes:
1669 1669 raise error.Abort(_('unknown stream compression type: %s')
1670 1670 % comp)
1671 1671 compengine = util.compengines.forbundletype(comp)
1672 1672 def chunkiter():
1673 1673 yield header
1674 1674 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1675 1675 yield chunk
1676 1676 chunkiter = chunkiter()
1677 1677
1678 1678 # parse the changegroup data, otherwise we will block
1679 1679 # in case of sshrepo because we don't know the end of the stream
1680 1680 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1681 1681
1682 1682 def combinechangegroupresults(op):
1683 1683 """logic to combine 0 or more addchangegroup results into one"""
1684 1684 results = [r.get('return', 0)
1685 1685 for r in op.records['changegroup']]
1686 1686 changedheads = 0
1687 1687 result = 1
1688 1688 for ret in results:
1689 1689 # If any changegroup result is 0, return 0
1690 1690 if ret == 0:
1691 1691 result = 0
1692 1692 break
1693 1693 if ret < -1:
1694 1694 changedheads += ret + 1
1695 1695 elif ret > 1:
1696 1696 changedheads += ret - 1
1697 1697 if changedheads > 0:
1698 1698 result = 1 + changedheads
1699 1699 elif changedheads < 0:
1700 1700 result = -1 + changedheads
1701 1701 return result
1702 1702
1703 1703 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1704 1704 'targetphase'))
1705 1705 def handlechangegroup(op, inpart):
1706 1706 """apply a changegroup part on the repo
1707 1707
1708 1708 This is a very early implementation that will massive rework before being
1709 1709 inflicted to any end-user.
1710 1710 """
1711 1711 tr = op.gettransaction()
1712 1712 unpackerversion = inpart.params.get('version', '01')
1713 1713 # We should raise an appropriate exception here
1714 1714 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1715 1715 # the source and url passed here are overwritten by the one contained in
1716 1716 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1717 1717 nbchangesets = None
1718 1718 if 'nbchanges' in inpart.params:
1719 1719 nbchangesets = int(inpart.params.get('nbchanges'))
1720 1720 if ('treemanifest' in inpart.params and
1721 1721 'treemanifest' not in op.repo.requirements):
1722 1722 if len(op.repo.changelog) != 0:
1723 1723 raise error.Abort(_(
1724 1724 "bundle contains tree manifests, but local repo is "
1725 1725 "non-empty and does not use tree manifests"))
1726 1726 op.repo.requirements.add('treemanifest')
1727 1727 op.repo._applyopenerreqs()
1728 1728 op.repo._writerequirements()
1729 1729 extrakwargs = {}
1730 1730 targetphase = inpart.params.get('targetphase')
1731 1731 if targetphase is not None:
1732 1732 extrakwargs['targetphase'] = int(targetphase)
1733 1733 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1734 1734 expectedtotal=nbchangesets, **extrakwargs)
1735 1735 if op.reply is not None:
1736 1736 # This is definitely not the final form of this
1737 1737 # return. But one need to start somewhere.
1738 1738 part = op.reply.newpart('reply:changegroup', mandatory=False)
1739 1739 part.addparam(
1740 1740 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1741 1741 part.addparam('return', '%i' % ret, mandatory=False)
1742 1742 assert not inpart.read()
1743 1743
1744 1744 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1745 1745 ['digest:%s' % k for k in util.DIGESTS.keys()])
1746 1746 @parthandler('remote-changegroup', _remotechangegroupparams)
1747 1747 def handleremotechangegroup(op, inpart):
1748 1748 """apply a bundle10 on the repo, given an url and validation information
1749 1749
1750 1750 All the information about the remote bundle to import are given as
1751 1751 parameters. The parameters include:
1752 1752 - url: the url to the bundle10.
1753 1753 - size: the bundle10 file size. It is used to validate what was
1754 1754 retrieved by the client matches the server knowledge about the bundle.
1755 1755 - digests: a space separated list of the digest types provided as
1756 1756 parameters.
1757 1757 - digest:<digest-type>: the hexadecimal representation of the digest with
1758 1758 that name. Like the size, it is used to validate what was retrieved by
1759 1759 the client matches what the server knows about the bundle.
1760 1760
1761 1761 When multiple digest types are given, all of them are checked.
1762 1762 """
1763 1763 try:
1764 1764 raw_url = inpart.params['url']
1765 1765 except KeyError:
1766 1766 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1767 1767 parsed_url = util.url(raw_url)
1768 1768 if parsed_url.scheme not in capabilities['remote-changegroup']:
1769 1769 raise error.Abort(_('remote-changegroup does not support %s urls') %
1770 1770 parsed_url.scheme)
1771 1771
1772 1772 try:
1773 1773 size = int(inpart.params['size'])
1774 1774 except ValueError:
1775 1775 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1776 1776 % 'size')
1777 1777 except KeyError:
1778 1778 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1779 1779
1780 1780 digests = {}
1781 1781 for typ in inpart.params.get('digests', '').split():
1782 1782 param = 'digest:%s' % typ
1783 1783 try:
1784 1784 value = inpart.params[param]
1785 1785 except KeyError:
1786 1786 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1787 1787 param)
1788 1788 digests[typ] = value
1789 1789
1790 1790 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1791 1791
1792 1792 tr = op.gettransaction()
1793 1793 from . import exchange
1794 1794 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1795 1795 if not isinstance(cg, changegroup.cg1unpacker):
1796 1796 raise error.Abort(_('%s: not a bundle version 1.0') %
1797 1797 util.hidepassword(raw_url))
1798 1798 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1799 1799 if op.reply is not None:
1800 1800 # This is definitely not the final form of this
1801 1801 # return. But one need to start somewhere.
1802 1802 part = op.reply.newpart('reply:changegroup')
1803 1803 part.addparam(
1804 1804 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1805 1805 part.addparam('return', '%i' % ret, mandatory=False)
1806 1806 try:
1807 1807 real_part.validate()
1808 1808 except error.Abort as e:
1809 1809 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1810 1810 (util.hidepassword(raw_url), str(e)))
1811 1811 assert not inpart.read()
1812 1812
1813 1813 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1814 1814 def handlereplychangegroup(op, inpart):
1815 1815 ret = int(inpart.params['return'])
1816 1816 replyto = int(inpart.params['in-reply-to'])
1817 1817 op.records.add('changegroup', {'return': ret}, replyto)
1818 1818
1819 1819 @parthandler('check:bookmarks')
1820 1820 def handlecheckbookmarks(op, inpart):
1821 1821 """check location of bookmarks
1822 1822
1823 1823 This part is to be used to detect push race regarding bookmark, it
1824 1824 contains binary encoded (bookmark, node) tuple. If the local state does
1825 1825 not marks the one in the part, a PushRaced exception is raised
1826 1826 """
1827 1827 bookdata = bookmarks.binarydecode(inpart)
1828 1828
1829 1829 msgstandard = ('repository changed while pushing - please try again '
1830 1830 '(bookmark "%s" move from %s to %s)')
1831 1831 msgmissing = ('repository changed while pushing - please try again '
1832 1832 '(bookmark "%s" is missing, expected %s)')
1833 1833 msgexist = ('repository changed while pushing - please try again '
1834 1834 '(bookmark "%s" set on %s, expected missing)')
1835 1835 for book, node in bookdata:
1836 1836 currentnode = op.repo._bookmarks.get(book)
1837 1837 if currentnode != node:
1838 1838 if node is None:
1839 1839 finalmsg = msgexist % (book, nodemod.short(currentnode))
1840 1840 elif currentnode is None:
1841 1841 finalmsg = msgmissing % (book, nodemod.short(node))
1842 1842 else:
1843 1843 finalmsg = msgstandard % (book, nodemod.short(node),
1844 1844 nodemod.short(currentnode))
1845 1845 raise error.PushRaced(finalmsg)
1846 1846
1847 1847 @parthandler('check:heads')
1848 1848 def handlecheckheads(op, inpart):
1849 1849 """check that head of the repo did not change
1850 1850
1851 1851 This is used to detect a push race when using unbundle.
1852 1852 This replaces the "heads" argument of unbundle."""
1853 1853 h = inpart.read(20)
1854 1854 heads = []
1855 1855 while len(h) == 20:
1856 1856 heads.append(h)
1857 1857 h = inpart.read(20)
1858 1858 assert not h
1859 1859 # Trigger a transaction so that we are guaranteed to have the lock now.
1860 1860 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1861 1861 op.gettransaction()
1862 1862 if sorted(heads) != sorted(op.repo.heads()):
1863 1863 raise error.PushRaced('repository changed while pushing - '
1864 1864 'please try again')
1865 1865
1866 1866 @parthandler('check:updated-heads')
1867 1867 def handlecheckupdatedheads(op, inpart):
1868 1868 """check for race on the heads touched by a push
1869 1869
1870 1870 This is similar to 'check:heads' but focus on the heads actually updated
1871 1871 during the push. If other activities happen on unrelated heads, it is
1872 1872 ignored.
1873 1873
1874 1874 This allow server with high traffic to avoid push contention as long as
1875 1875 unrelated parts of the graph are involved."""
1876 1876 h = inpart.read(20)
1877 1877 heads = []
1878 1878 while len(h) == 20:
1879 1879 heads.append(h)
1880 1880 h = inpart.read(20)
1881 1881 assert not h
1882 1882 # trigger a transaction so that we are guaranteed to have the lock now.
1883 1883 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1884 1884 op.gettransaction()
1885 1885
1886 1886 currentheads = set()
1887 1887 for ls in op.repo.branchmap().itervalues():
1888 1888 currentheads.update(ls)
1889 1889
1890 1890 for h in heads:
1891 1891 if h not in currentheads:
1892 1892 raise error.PushRaced('repository changed while pushing - '
1893 1893 'please try again')
1894 1894
1895 1895 @parthandler('check:phases')
1896 1896 def handlecheckphases(op, inpart):
1897 1897 """check that phase boundaries of the repository did not change
1898 1898
1899 1899 This is used to detect a push race.
1900 1900 """
1901 1901 phasetonodes = phases.binarydecode(inpart)
1902 1902 unfi = op.repo.unfiltered()
1903 1903 cl = unfi.changelog
1904 1904 phasecache = unfi._phasecache
1905 1905 msg = ('repository changed while pushing - please try again '
1906 1906 '(%s is %s expected %s)')
1907 1907 for expectedphase, nodes in enumerate(phasetonodes):
1908 1908 for n in nodes:
1909 1909 actualphase = phasecache.phase(unfi, cl.rev(n))
1910 1910 if actualphase != expectedphase:
1911 1911 finalmsg = msg % (nodemod.short(n),
1912 1912 phases.phasenames[actualphase],
1913 1913 phases.phasenames[expectedphase])
1914 1914 raise error.PushRaced(finalmsg)
1915 1915
1916 1916 @parthandler('output')
1917 1917 def handleoutput(op, inpart):
1918 1918 """forward output captured on the server to the client"""
1919 1919 for line in inpart.read().splitlines():
1920 1920 op.ui.status(_('remote: %s\n') % line)
1921 1921
1922 1922 @parthandler('replycaps')
1923 1923 def handlereplycaps(op, inpart):
1924 1924 """Notify that a reply bundle should be created
1925 1925
1926 1926 The payload contains the capabilities information for the reply"""
1927 1927 caps = decodecaps(inpart.read())
1928 1928 if op.reply is None:
1929 1929 op.reply = bundle20(op.ui, caps)
1930 1930
1931 1931 class AbortFromPart(error.Abort):
1932 1932 """Sub-class of Abort that denotes an error from a bundle2 part."""
1933 1933
1934 1934 @parthandler('error:abort', ('message', 'hint'))
1935 1935 def handleerrorabort(op, inpart):
1936 1936 """Used to transmit abort error over the wire"""
1937 1937 raise AbortFromPart(inpart.params['message'],
1938 1938 hint=inpart.params.get('hint'))
1939 1939
1940 1940 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
1941 1941 'in-reply-to'))
1942 1942 def handleerrorpushkey(op, inpart):
1943 1943 """Used to transmit failure of a mandatory pushkey over the wire"""
1944 1944 kwargs = {}
1945 1945 for name in ('namespace', 'key', 'new', 'old', 'ret'):
1946 1946 value = inpart.params.get(name)
1947 1947 if value is not None:
1948 1948 kwargs[name] = value
1949 1949 raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
1950 1950
1951 1951 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1952 1952 def handleerrorunsupportedcontent(op, inpart):
1953 1953 """Used to transmit unknown content error over the wire"""
1954 1954 kwargs = {}
1955 1955 parttype = inpart.params.get('parttype')
1956 1956 if parttype is not None:
1957 1957 kwargs['parttype'] = parttype
1958 1958 params = inpart.params.get('params')
1959 1959 if params is not None:
1960 1960 kwargs['params'] = params.split('\0')
1961 1961
1962 1962 raise error.BundleUnknownFeatureError(**kwargs)
1963 1963
1964 1964 @parthandler('error:pushraced', ('message',))
1965 1965 def handleerrorpushraced(op, inpart):
1966 1966 """Used to transmit push race error over the wire"""
1967 1967 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1968 1968
1969 1969 @parthandler('listkeys', ('namespace',))
1970 1970 def handlelistkeys(op, inpart):
1971 1971 """retrieve pushkey namespace content stored in a bundle2"""
1972 1972 namespace = inpart.params['namespace']
1973 1973 r = pushkey.decodekeys(inpart.read())
1974 1974 op.records.add('listkeys', (namespace, r))
1975 1975
1976 1976 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1977 1977 def handlepushkey(op, inpart):
1978 1978 """process a pushkey request"""
1979 1979 dec = pushkey.decode
1980 1980 namespace = dec(inpart.params['namespace'])
1981 1981 key = dec(inpart.params['key'])
1982 1982 old = dec(inpart.params['old'])
1983 1983 new = dec(inpart.params['new'])
1984 1984 # Grab the transaction to ensure that we have the lock before performing the
1985 1985 # pushkey.
1986 1986 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1987 1987 op.gettransaction()
1988 1988 ret = op.repo.pushkey(namespace, key, old, new)
1989 1989 record = {'namespace': namespace,
1990 1990 'key': key,
1991 1991 'old': old,
1992 1992 'new': new}
1993 1993 op.records.add('pushkey', record)
1994 1994 if op.reply is not None:
1995 1995 rpart = op.reply.newpart('reply:pushkey')
1996 1996 rpart.addparam(
1997 1997 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1998 1998 rpart.addparam('return', '%i' % ret, mandatory=False)
1999 1999 if inpart.mandatory and not ret:
2000 2000 kwargs = {}
2001 2001 for key in ('namespace', 'key', 'new', 'old', 'ret'):
2002 2002 if key in inpart.params:
2003 2003 kwargs[key] = inpart.params[key]
2004 2004 raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
2005 2005
2006 2006 @parthandler('bookmarks')
2007 2007 def handlebookmark(op, inpart):
2008 2008 """transmit bookmark information
2009 2009
2010 2010 The part contains binary encoded bookmark information.
2011 2011
2012 2012 The exact behavior of this part can be controlled by the 'bookmarks' mode
2013 2013 on the bundle operation.
2014 2014
2015 2015 When mode is 'apply' (the default) the bookmark information is applied as
2016 2016 is to the unbundling repository. Make sure a 'check:bookmarks' part is
2017 2017 issued earlier to check for push races in such update. This behavior is
2018 2018 suitable for pushing.
2019 2019
2020 2020 When mode is 'records', the information is recorded into the 'bookmarks'
2021 2021 records of the bundle operation. This behavior is suitable for pulling.
2022 2022 """
2023 2023 changes = bookmarks.binarydecode(inpart)
2024 2024
2025 2025 pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
2026 2026 bookmarksmode = op.modes.get('bookmarks', 'apply')
2027 2027
2028 2028 if bookmarksmode == 'apply':
2029 2029 tr = op.gettransaction()
2030 2030 bookstore = op.repo._bookmarks
2031 2031 if pushkeycompat:
2032 2032 allhooks = []
2033 2033 for book, node in changes:
2034 2034 hookargs = tr.hookargs.copy()
2035 2035 hookargs['pushkeycompat'] = '1'
2036 hookargs['namespace'] = 'bookmark'
2036 hookargs['namespace'] = 'bookmarks'
2037 2037 hookargs['key'] = book
2038 2038 hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
2039 2039 hookargs['new'] = nodemod.hex(node if node is not None else '')
2040 2040 allhooks.append(hookargs)
2041 2041
2042 2042 for hookargs in allhooks:
2043 2043 op.repo.hook('prepushkey', throw=True, **hookargs)
2044 2044
2045 2045 bookstore.applychanges(op.repo, op.gettransaction(), changes)
2046 2046
2047 2047 if pushkeycompat:
2048 2048 def runhook():
2049 2049 for hookargs in allhooks:
2050 2050 op.repo.hook('pushkey', **hookargs)
2051 2051 op.repo._afterlock(runhook)
2052 2052
2053 2053 elif bookmarksmode == 'records':
2054 2054 for book, node in changes:
2055 2055 record = {'bookmark': book, 'node': node}
2056 2056 op.records.add('bookmarks', record)
2057 2057 else:
2058 2058 raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode)
2059 2059
2060 2060 @parthandler('phase-heads')
2061 2061 def handlephases(op, inpart):
2062 2062 """apply phases from bundle part to repo"""
2063 2063 headsbyphase = phases.binarydecode(inpart)
2064 2064 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
2065 2065
2066 2066 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
2067 2067 def handlepushkeyreply(op, inpart):
2068 2068 """retrieve the result of a pushkey request"""
2069 2069 ret = int(inpart.params['return'])
2070 2070 partid = int(inpart.params['in-reply-to'])
2071 2071 op.records.add('pushkey', {'return': ret}, partid)
2072 2072
2073 2073 @parthandler('obsmarkers')
2074 2074 def handleobsmarker(op, inpart):
2075 2075 """add a stream of obsmarkers to the repo"""
2076 2076 tr = op.gettransaction()
2077 2077 markerdata = inpart.read()
2078 2078 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
2079 2079 op.ui.write(('obsmarker-exchange: %i bytes received\n')
2080 2080 % len(markerdata))
2081 2081 # The mergemarkers call will crash if marker creation is not enabled.
2082 2082 # we want to avoid this if the part is advisory.
2083 2083 if not inpart.mandatory and op.repo.obsstore.readonly:
2084 2084 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n')
2085 2085 return
2086 2086 new = op.repo.obsstore.mergemarkers(tr, markerdata)
2087 2087 op.repo.invalidatevolatilesets()
2088 2088 if new:
2089 2089 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
2090 2090 op.records.add('obsmarkers', {'new': new})
2091 2091 if op.reply is not None:
2092 2092 rpart = op.reply.newpart('reply:obsmarkers')
2093 2093 rpart.addparam(
2094 2094 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2095 2095 rpart.addparam('new', '%i' % new, mandatory=False)
2096 2096
2097 2097
2098 2098 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
2099 2099 def handleobsmarkerreply(op, inpart):
2100 2100 """retrieve the result of a pushkey request"""
2101 2101 ret = int(inpart.params['new'])
2102 2102 partid = int(inpart.params['in-reply-to'])
2103 2103 op.records.add('obsmarkers', {'new': ret}, partid)
2104 2104
2105 2105 @parthandler('hgtagsfnodes')
2106 2106 def handlehgtagsfnodes(op, inpart):
2107 2107 """Applies .hgtags fnodes cache entries to the local repo.
2108 2108
2109 2109 Payload is pairs of 20 byte changeset nodes and filenodes.
2110 2110 """
2111 2111 # Grab the transaction so we ensure that we have the lock at this point.
2112 2112 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2113 2113 op.gettransaction()
2114 2114 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
2115 2115
2116 2116 count = 0
2117 2117 while True:
2118 2118 node = inpart.read(20)
2119 2119 fnode = inpart.read(20)
2120 2120 if len(node) < 20 or len(fnode) < 20:
2121 2121 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
2122 2122 break
2123 2123 cache.setfnode(node, fnode)
2124 2124 count += 1
2125 2125
2126 2126 cache.write()
2127 2127 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
2128 2128
2129 2129 @parthandler('pushvars')
2130 2130 def bundle2getvars(op, part):
2131 2131 '''unbundle a bundle2 containing shellvars on the server'''
2132 2132 # An option to disable unbundling on server-side for security reasons
2133 2133 if op.ui.configbool('push', 'pushvars.server'):
2134 2134 hookargs = {}
2135 2135 for key, value in part.advisoryparams:
2136 2136 key = key.upper()
2137 2137 # We want pushed variables to have USERVAR_ prepended so we know
2138 2138 # they came from the --pushvar flag.
2139 2139 key = "USERVAR_" + key
2140 2140 hookargs[key] = value
2141 2141 op.addhookargs(hookargs)
2142 2142
2143 2143 @parthandler('stream2', ('requirements', 'filecount', 'bytecount'))
2144 2144 def handlestreamv2bundle(op, part):
2145 2145
2146 2146 requirements = part.params['requirements'].split()
2147 2147 filecount = int(part.params['filecount'])
2148 2148 bytecount = int(part.params['bytecount'])
2149 2149
2150 2150 repo = op.repo
2151 2151 if len(repo):
2152 2152 msg = _('cannot apply stream clone to non empty repository')
2153 2153 raise error.Abort(msg)
2154 2154
2155 2155 repo.ui.debug('applying stream bundle\n')
2156 2156 streamclone.applybundlev2(repo, part, filecount, bytecount,
2157 2157 requirements)
@@ -1,1175 +1,1244
1 1 #testcases b2-pushkey b2-binary
2 2
3 3 #if b2-pushkey
4 4 $ cat << EOF >> $HGRCPATH
5 5 > [devel]
6 6 > legacy.exchange=bookmarks
7 7 > EOF
8 8 #endif
9 9
10 10 #require serve
11 11
12 12 $ cat << EOF >> $HGRCPATH
13 13 > [ui]
14 14 > logtemplate={rev}:{node|short} {desc|firstline}
15 15 > [phases]
16 16 > publish=False
17 17 > [experimental]
18 18 > evolution.createmarkers=True
19 19 > evolution.exchange=True
20 20 > EOF
21 21
22 22 $ cat > $TESTTMP/hook.sh <<'EOF'
23 23 > echo "test-hook-bookmark: $HG_BOOKMARK: $HG_OLDNODE -> $HG_NODE"
24 24 > EOF
25 25 $ TESTHOOK="hooks.txnclose-bookmark.test=sh $TESTTMP/hook.sh"
26 26
27 27 initialize
28 28
29 29 $ hg init a
30 30 $ cd a
31 31 $ echo 'test' > test
32 32 $ hg commit -Am'test'
33 33 adding test
34 34
35 35 set bookmarks
36 36
37 37 $ hg bookmark X
38 38 $ hg bookmark Y
39 39 $ hg bookmark Z
40 40
41 41 import bookmark by name
42 42
43 43 $ hg init ../b
44 44 $ cd ../b
45 45 $ hg book Y
46 46 $ hg book
47 47 * Y -1:000000000000
48 48 $ hg pull ../a --config "$TESTHOOK"
49 49 pulling from ../a
50 50 requesting all changes
51 51 adding changesets
52 52 adding manifests
53 53 adding file changes
54 54 added 1 changesets with 1 changes to 1 files
55 55 adding remote bookmark X
56 56 updating bookmark Y
57 57 adding remote bookmark Z
58 58 new changesets 4e3505fd9583
59 59 test-hook-bookmark: X: -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
60 60 test-hook-bookmark: Y: 0000000000000000000000000000000000000000 -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
61 61 test-hook-bookmark: Z: -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
62 62 (run 'hg update' to get a working copy)
63 63 $ hg bookmarks
64 64 X 0:4e3505fd9583
65 65 * Y 0:4e3505fd9583
66 66 Z 0:4e3505fd9583
67 67 $ hg debugpushkey ../a namespaces
68 68 bookmarks
69 69 namespaces
70 70 obsolete
71 71 phases
72 72 $ hg debugpushkey ../a bookmarks
73 73 X 4e3505fd95835d721066b76e75dbb8cc554d7f77
74 74 Y 4e3505fd95835d721066b76e75dbb8cc554d7f77
75 75 Z 4e3505fd95835d721066b76e75dbb8cc554d7f77
76 76
77 77 delete the bookmark to re-pull it
78 78
79 79 $ hg book -d X
80 80 $ hg pull -B X ../a
81 81 pulling from ../a
82 82 no changes found
83 83 adding remote bookmark X
84 84
85 85 finally no-op pull
86 86
87 87 $ hg pull -B X ../a
88 88 pulling from ../a
89 89 no changes found
90 90 $ hg bookmark
91 91 X 0:4e3505fd9583
92 92 * Y 0:4e3505fd9583
93 93 Z 0:4e3505fd9583
94 94
95 95 export bookmark by name
96 96
97 97 $ hg bookmark W
98 98 $ hg bookmark foo
99 99 $ hg bookmark foobar
100 100 $ hg push -B W ../a
101 101 pushing to ../a
102 102 searching for changes
103 103 no changes found
104 104 exporting bookmark W
105 105 [1]
106 106 $ hg -R ../a bookmarks
107 107 W -1:000000000000
108 108 X 0:4e3505fd9583
109 109 Y 0:4e3505fd9583
110 110 * Z 0:4e3505fd9583
111 111
112 112 delete a remote bookmark
113 113
114 114 $ hg book -d W
115 115
116 116 #if b2-pushkey
117 117
118 118 $ hg push -B W ../a --config "$TESTHOOK" --debug --config devel.bundle2.debug=yes
119 119 pushing to ../a
120 120 query 1; heads
121 121 searching for changes
122 122 all remote heads known locally
123 123 listing keys for "phases"
124 124 checking for updated bookmarks
125 125 listing keys for "bookmarks"
126 126 no changes found
127 127 bundle2-output-bundle: "HG20", 4 parts total
128 128 bundle2-output: start emission of HG20 stream
129 129 bundle2-output: bundle parameter:
130 130 bundle2-output: start of parts
131 131 bundle2-output: bundle part: "replycaps"
132 132 bundle2-output-part: "replycaps" 205 bytes payload
133 133 bundle2-output: part 0: "REPLYCAPS"
134 134 bundle2-output: header chunk size: 16
135 135 bundle2-output: payload chunk size: 205
136 136 bundle2-output: closing payload chunk
137 137 bundle2-output: bundle part: "check:bookmarks"
138 138 bundle2-output-part: "check:bookmarks" 23 bytes payload
139 139 bundle2-output: part 1: "CHECK:BOOKMARKS"
140 140 bundle2-output: header chunk size: 22
141 141 bundle2-output: payload chunk size: 23
142 142 bundle2-output: closing payload chunk
143 143 bundle2-output: bundle part: "check:phases"
144 144 bundle2-output-part: "check:phases" 48 bytes payload
145 145 bundle2-output: part 2: "CHECK:PHASES"
146 146 bundle2-output: header chunk size: 19
147 147 bundle2-output: payload chunk size: 48
148 148 bundle2-output: closing payload chunk
149 149 bundle2-output: bundle part: "pushkey"
150 150 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
151 151 bundle2-output: part 3: "PUSHKEY"
152 152 bundle2-output: header chunk size: 90
153 153 bundle2-output: closing payload chunk
154 154 bundle2-output: end of bundle
155 155 bundle2-input: start processing of HG20 stream
156 156 bundle2-input: reading bundle2 stream parameters
157 157 bundle2-input-bundle: with-transaction
158 158 bundle2-input: start extraction of bundle2 parts
159 159 bundle2-input: part header size: 16
160 160 bundle2-input: part type: "REPLYCAPS"
161 161 bundle2-input: part id: "0"
162 162 bundle2-input: part parameters: 0
163 163 bundle2-input: found a handler for part replycaps
164 164 bundle2-input-part: "replycaps" supported
165 165 bundle2-input: payload chunk size: 205
166 166 bundle2-input: payload chunk size: 0
167 167 bundle2-input-part: total payload size 205
168 168 bundle2-input: part header size: 22
169 169 bundle2-input: part type: "CHECK:BOOKMARKS"
170 170 bundle2-input: part id: "1"
171 171 bundle2-input: part parameters: 0
172 172 bundle2-input: found a handler for part check:bookmarks
173 173 bundle2-input-part: "check:bookmarks" supported
174 174 bundle2-input: payload chunk size: 23
175 175 bundle2-input: payload chunk size: 0
176 176 bundle2-input-part: total payload size 23
177 177 bundle2-input: part header size: 19
178 178 bundle2-input: part type: "CHECK:PHASES"
179 179 bundle2-input: part id: "2"
180 180 bundle2-input: part parameters: 0
181 181 bundle2-input: found a handler for part check:phases
182 182 bundle2-input-part: "check:phases" supported
183 183 bundle2-input: payload chunk size: 48
184 184 bundle2-input: payload chunk size: 0
185 185 bundle2-input-part: total payload size 48
186 186 bundle2-input: part header size: 90
187 187 bundle2-input: part type: "PUSHKEY"
188 188 bundle2-input: part id: "3"
189 189 bundle2-input: part parameters: 4
190 190 bundle2-input: found a handler for part pushkey
191 191 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
192 192 pushing key for "bookmarks:W"
193 193 bundle2-input: payload chunk size: 0
194 194 bundle2-input: part header size: 0
195 195 bundle2-input: end of bundle2 stream
196 196 bundle2-input-bundle: 3 parts total
197 197 running hook txnclose-bookmark.test: sh $TESTTMP/hook.sh
198 198 test-hook-bookmark: W: 0000000000000000000000000000000000000000 ->
199 199 bundle2-output-bundle: "HG20", 1 parts total
200 200 bundle2-output: start emission of HG20 stream
201 201 bundle2-output: bundle parameter:
202 202 bundle2-output: start of parts
203 203 bundle2-output: bundle part: "reply:pushkey"
204 204 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
205 205 bundle2-output: part 0: "REPLY:PUSHKEY"
206 206 bundle2-output: header chunk size: 43
207 207 bundle2-output: closing payload chunk
208 208 bundle2-output: end of bundle
209 209 bundle2-input: start processing of HG20 stream
210 210 bundle2-input: reading bundle2 stream parameters
211 211 bundle2-input-bundle: no-transaction
212 212 bundle2-input: start extraction of bundle2 parts
213 213 bundle2-input: part header size: 43
214 214 bundle2-input: part type: "REPLY:PUSHKEY"
215 215 bundle2-input: part id: "0"
216 216 bundle2-input: part parameters: 2
217 217 bundle2-input: found a handler for part reply:pushkey
218 218 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
219 219 bundle2-input: payload chunk size: 0
220 220 bundle2-input: part header size: 0
221 221 bundle2-input: end of bundle2 stream
222 222 bundle2-input-bundle: 0 parts total
223 223 deleting remote bookmark W
224 224 listing keys for "phases"
225 225 [1]
226 226
227 227 #endif
228 228 #if b2-binary
229 229
230 230 $ hg push -B W ../a --config "$TESTHOOK" --debug --config devel.bundle2.debug=yes
231 231 pushing to ../a
232 232 query 1; heads
233 233 searching for changes
234 234 all remote heads known locally
235 235 listing keys for "phases"
236 236 checking for updated bookmarks
237 237 listing keys for "bookmarks"
238 238 no changes found
239 239 bundle2-output-bundle: "HG20", 4 parts total
240 240 bundle2-output: start emission of HG20 stream
241 241 bundle2-output: bundle parameter:
242 242 bundle2-output: start of parts
243 243 bundle2-output: bundle part: "replycaps"
244 244 bundle2-output-part: "replycaps" 205 bytes payload
245 245 bundle2-output: part 0: "REPLYCAPS"
246 246 bundle2-output: header chunk size: 16
247 247 bundle2-output: payload chunk size: 205
248 248 bundle2-output: closing payload chunk
249 249 bundle2-output: bundle part: "check:bookmarks"
250 250 bundle2-output-part: "check:bookmarks" 23 bytes payload
251 251 bundle2-output: part 1: "CHECK:BOOKMARKS"
252 252 bundle2-output: header chunk size: 22
253 253 bundle2-output: payload chunk size: 23
254 254 bundle2-output: closing payload chunk
255 255 bundle2-output: bundle part: "check:phases"
256 256 bundle2-output-part: "check:phases" 48 bytes payload
257 257 bundle2-output: part 2: "CHECK:PHASES"
258 258 bundle2-output: header chunk size: 19
259 259 bundle2-output: payload chunk size: 48
260 260 bundle2-output: closing payload chunk
261 261 bundle2-output: bundle part: "bookmarks"
262 262 bundle2-output-part: "bookmarks" 23 bytes payload
263 263 bundle2-output: part 3: "BOOKMARKS"
264 264 bundle2-output: header chunk size: 16
265 265 bundle2-output: payload chunk size: 23
266 266 bundle2-output: closing payload chunk
267 267 bundle2-output: end of bundle
268 268 bundle2-input: start processing of HG20 stream
269 269 bundle2-input: reading bundle2 stream parameters
270 270 bundle2-input-bundle: with-transaction
271 271 bundle2-input: start extraction of bundle2 parts
272 272 bundle2-input: part header size: 16
273 273 bundle2-input: part type: "REPLYCAPS"
274 274 bundle2-input: part id: "0"
275 275 bundle2-input: part parameters: 0
276 276 bundle2-input: found a handler for part replycaps
277 277 bundle2-input-part: "replycaps" supported
278 278 bundle2-input: payload chunk size: 205
279 279 bundle2-input: payload chunk size: 0
280 280 bundle2-input-part: total payload size 205
281 281 bundle2-input: part header size: 22
282 282 bundle2-input: part type: "CHECK:BOOKMARKS"
283 283 bundle2-input: part id: "1"
284 284 bundle2-input: part parameters: 0
285 285 bundle2-input: found a handler for part check:bookmarks
286 286 bundle2-input-part: "check:bookmarks" supported
287 287 bundle2-input: payload chunk size: 23
288 288 bundle2-input: payload chunk size: 0
289 289 bundle2-input-part: total payload size 23
290 290 bundle2-input: part header size: 19
291 291 bundle2-input: part type: "CHECK:PHASES"
292 292 bundle2-input: part id: "2"
293 293 bundle2-input: part parameters: 0
294 294 bundle2-input: found a handler for part check:phases
295 295 bundle2-input-part: "check:phases" supported
296 296 bundle2-input: payload chunk size: 48
297 297 bundle2-input: payload chunk size: 0
298 298 bundle2-input-part: total payload size 48
299 299 bundle2-input: part header size: 16
300 300 bundle2-input: part type: "BOOKMARKS"
301 301 bundle2-input: part id: "3"
302 302 bundle2-input: part parameters: 0
303 303 bundle2-input: found a handler for part bookmarks
304 304 bundle2-input-part: "bookmarks" supported
305 305 bundle2-input: payload chunk size: 23
306 306 bundle2-input: payload chunk size: 0
307 307 bundle2-input-part: total payload size 23
308 308 bundle2-input: part header size: 0
309 309 bundle2-input: end of bundle2 stream
310 310 bundle2-input-bundle: 3 parts total
311 311 running hook txnclose-bookmark.test: sh $TESTTMP/hook.sh
312 312 test-hook-bookmark: W: 0000000000000000000000000000000000000000 ->
313 313 bundle2-output-bundle: "HG20", 0 parts total
314 314 bundle2-output: start emission of HG20 stream
315 315 bundle2-output: bundle parameter:
316 316 bundle2-output: start of parts
317 317 bundle2-output: end of bundle
318 318 bundle2-input: start processing of HG20 stream
319 319 bundle2-input: reading bundle2 stream parameters
320 320 bundle2-input-bundle: no-transaction
321 321 bundle2-input: start extraction of bundle2 parts
322 322 bundle2-input: part header size: 0
323 323 bundle2-input: end of bundle2 stream
324 324 bundle2-input-bundle: 0 parts total
325 325 deleting remote bookmark W
326 326 listing keys for "phases"
327 327 [1]
328 328
329 329 #endif
330 330
331 331 export the active bookmark
332 332
333 333 $ hg bookmark V
334 334 $ hg push -B . ../a
335 335 pushing to ../a
336 336 searching for changes
337 337 no changes found
338 338 exporting bookmark V
339 339 [1]
340 340
341 341 exporting the active bookmark with 'push -B .'
342 342 demand that one of the bookmarks is activated
343 343
344 344 $ hg update -r default
345 345 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 346 (leaving bookmark V)
347 347 $ hg push -B . ../a
348 348 abort: no active bookmark
349 349 [255]
350 350 $ hg update -r V
351 351 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
352 352 (activating bookmark V)
353 353
354 354 delete the bookmark
355 355
356 356 $ hg book -d V
357 357 $ hg push -B V ../a
358 358 pushing to ../a
359 359 searching for changes
360 360 no changes found
361 361 deleting remote bookmark V
362 362 [1]
363 363 $ hg up foobar
364 364 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 365 (activating bookmark foobar)
366 366
367 367 push/pull name that doesn't exist
368 368
369 369 $ hg push -B badname ../a
370 370 pushing to ../a
371 371 searching for changes
372 372 bookmark badname does not exist on the local or remote repository!
373 373 no changes found
374 374 [2]
375 375 $ hg pull -B anotherbadname ../a
376 376 pulling from ../a
377 377 abort: remote bookmark anotherbadname not found!
378 378 [255]
379 379
380 380 divergent bookmarks
381 381
382 382 $ cd ../a
383 383 $ echo c1 > f1
384 384 $ hg ci -Am1
385 385 adding f1
386 386 $ hg book -f @
387 387 $ hg book -f X
388 388 $ hg book
389 389 @ 1:0d2164f0ce0d
390 390 * X 1:0d2164f0ce0d
391 391 Y 0:4e3505fd9583
392 392 Z 1:0d2164f0ce0d
393 393
394 394 $ cd ../b
395 395 $ hg up
396 396 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
397 397 updating bookmark foobar
398 398 $ echo c2 > f2
399 399 $ hg ci -Am2
400 400 adding f2
401 401 $ hg book -if @
402 402 $ hg book -if X
403 403 $ hg book
404 404 @ 1:9b140be10808
405 405 X 1:9b140be10808
406 406 Y 0:4e3505fd9583
407 407 Z 0:4e3505fd9583
408 408 foo -1:000000000000
409 409 * foobar 1:9b140be10808
410 410
411 411 $ hg pull --config paths.foo=../a foo --config "$TESTHOOK"
412 412 pulling from $TESTTMP/a
413 413 searching for changes
414 414 adding changesets
415 415 adding manifests
416 416 adding file changes
417 417 added 1 changesets with 1 changes to 1 files (+1 heads)
418 418 divergent bookmark @ stored as @foo
419 419 divergent bookmark X stored as X@foo
420 420 updating bookmark Z
421 421 new changesets 0d2164f0ce0d
422 422 test-hook-bookmark: @foo: -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
423 423 test-hook-bookmark: X@foo: -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
424 424 test-hook-bookmark: Z: 4e3505fd95835d721066b76e75dbb8cc554d7f77 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
425 425 (run 'hg heads' to see heads, 'hg merge' to merge)
426 426 $ hg book
427 427 @ 1:9b140be10808
428 428 @foo 2:0d2164f0ce0d
429 429 X 1:9b140be10808
430 430 X@foo 2:0d2164f0ce0d
431 431 Y 0:4e3505fd9583
432 432 Z 2:0d2164f0ce0d
433 433 foo -1:000000000000
434 434 * foobar 1:9b140be10808
435 435
436 436 (test that too many divergence of bookmark)
437 437
438 438 $ $PYTHON $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -r 000000000000 "X@${i}"; done
439 439 $ hg pull ../a
440 440 pulling from ../a
441 441 searching for changes
442 442 no changes found
443 443 warning: failed to assign numbered name to divergent bookmark X
444 444 divergent bookmark @ stored as @1
445 445 $ hg bookmarks | grep '^ X' | grep -v ':000000000000'
446 446 X 1:9b140be10808
447 447 X@foo 2:0d2164f0ce0d
448 448
449 449 (test that remotely diverged bookmarks are reused if they aren't changed)
450 450
451 451 $ hg bookmarks | grep '^ @'
452 452 @ 1:9b140be10808
453 453 @1 2:0d2164f0ce0d
454 454 @foo 2:0d2164f0ce0d
455 455 $ hg pull ../a
456 456 pulling from ../a
457 457 searching for changes
458 458 no changes found
459 459 warning: failed to assign numbered name to divergent bookmark X
460 460 divergent bookmark @ stored as @1
461 461 $ hg bookmarks | grep '^ @'
462 462 @ 1:9b140be10808
463 463 @1 2:0d2164f0ce0d
464 464 @foo 2:0d2164f0ce0d
465 465
466 466 $ $PYTHON $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -d "X@${i}"; done
467 467 $ hg bookmarks -d "@1"
468 468
469 469 $ hg push -f ../a
470 470 pushing to ../a
471 471 searching for changes
472 472 adding changesets
473 473 adding manifests
474 474 adding file changes
475 475 added 1 changesets with 1 changes to 1 files (+1 heads)
476 476 $ hg -R ../a book
477 477 @ 1:0d2164f0ce0d
478 478 * X 1:0d2164f0ce0d
479 479 Y 0:4e3505fd9583
480 480 Z 1:0d2164f0ce0d
481 481
482 482 explicit pull should overwrite the local version (issue4439)
483 483
484 484 $ hg update -r X
485 485 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 486 (activating bookmark X)
487 487 $ hg pull --config paths.foo=../a foo -B . --config "$TESTHOOK"
488 488 pulling from $TESTTMP/a
489 489 no changes found
490 490 divergent bookmark @ stored as @foo
491 491 importing bookmark X
492 492 test-hook-bookmark: @foo: 0d2164f0ce0d8f1d6f94351eba04b794909be66c -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
493 493 test-hook-bookmark: X: 9b140be1080824d768c5a4691a564088eede71f9 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
494 494
495 495 reinstall state for further testing:
496 496
497 497 $ hg book -fr 9b140be10808 X
498 498
499 499 revsets should not ignore divergent bookmarks
500 500
501 501 $ hg bookmark -fr 1 Z
502 502 $ hg log -r 'bookmark()' --template '{rev}:{node|short} {bookmarks}\n'
503 503 0:4e3505fd9583 Y
504 504 1:9b140be10808 @ X Z foobar
505 505 2:0d2164f0ce0d @foo X@foo
506 506 $ hg log -r 'bookmark("X@foo")' --template '{rev}:{node|short} {bookmarks}\n'
507 507 2:0d2164f0ce0d @foo X@foo
508 508 $ hg log -r 'bookmark("re:X@foo")' --template '{rev}:{node|short} {bookmarks}\n'
509 509 2:0d2164f0ce0d @foo X@foo
510 510
511 511 update a remote bookmark from a non-head to a head
512 512
513 513 $ hg up -q Y
514 514 $ echo c3 > f2
515 515 $ hg ci -Am3
516 516 adding f2
517 517 created new head
518 518 $ hg push ../a --config "$TESTHOOK"
519 519 pushing to ../a
520 520 searching for changes
521 521 adding changesets
522 522 adding manifests
523 523 adding file changes
524 524 added 1 changesets with 1 changes to 1 files (+1 heads)
525 525 test-hook-bookmark: Y: 4e3505fd95835d721066b76e75dbb8cc554d7f77 -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
526 526 updating bookmark Y
527 527 $ hg -R ../a book
528 528 @ 1:0d2164f0ce0d
529 529 * X 1:0d2164f0ce0d
530 530 Y 3:f6fc62dde3c0
531 531 Z 1:0d2164f0ce0d
532 532
533 533 update a bookmark in the middle of a client pulling changes
534 534
535 535 $ cd ..
536 536 $ hg clone -q a pull-race
537 537
538 538 We want to use http because it is stateless and therefore more susceptible to
539 539 race conditions
540 540
541 541 $ hg serve -R pull-race -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log
542 542 $ cat pull-race.pid >> $DAEMON_PIDS
543 543
544 544 $ cat <<EOF > $TESTTMP/out_makecommit.sh
545 545 > #!/bin/sh
546 546 > hg ci -Am5
547 547 > echo committed in pull-race
548 548 > EOF
549 549
550 550 $ hg clone -q http://localhost:$HGPORT/ pull-race2 --config "$TESTHOOK"
551 551 test-hook-bookmark: @: -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
552 552 test-hook-bookmark: X: -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
553 553 test-hook-bookmark: Y: -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
554 554 test-hook-bookmark: Z: -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
555 555 $ cd pull-race
556 556 $ hg up -q Y
557 557 $ echo c4 > f2
558 558 $ hg ci -Am4
559 559 $ echo c5 > f3
560 560 $ cat <<EOF > .hg/hgrc
561 561 > [hooks]
562 562 > outgoing.makecommit = sh $TESTTMP/out_makecommit.sh
563 563 > EOF
564 564
565 565 (new config needs a server restart)
566 566
567 567 $ cd ..
568 568 $ killdaemons.py
569 569 $ hg serve -R pull-race -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log
570 570 $ cat pull-race.pid >> $DAEMON_PIDS
571 571 $ cd pull-race2
572 572 $ hg -R $TESTTMP/pull-race book
573 573 @ 1:0d2164f0ce0d
574 574 X 1:0d2164f0ce0d
575 575 * Y 4:b0a5eff05604
576 576 Z 1:0d2164f0ce0d
577 577 $ hg pull
578 578 pulling from http://localhost:$HGPORT/
579 579 searching for changes
580 580 adding changesets
581 581 adding manifests
582 582 adding file changes
583 583 added 1 changesets with 1 changes to 1 files
584 584 updating bookmark Y
585 585 new changesets b0a5eff05604
586 586 (run 'hg update' to get a working copy)
587 587 $ hg book
588 588 * @ 1:0d2164f0ce0d
589 589 X 1:0d2164f0ce0d
590 590 Y 4:b0a5eff05604
591 591 Z 1:0d2164f0ce0d
592 592
593 593 Update a bookmark right after the initial lookup -B (issue4689)
594 594
595 595 $ echo c6 > ../pull-race/f3 # to be committed during the race
596 596 $ cat <<EOF > $TESTTMP/listkeys_makecommit.sh
597 597 > #!/bin/sh
598 598 > if hg st | grep -q M; then
599 599 > hg commit -m race
600 600 > echo committed in pull-race
601 601 > else
602 602 > exit 0
603 603 > fi
604 604 > EOF
605 605 $ cat <<EOF > ../pull-race/.hg/hgrc
606 606 > [hooks]
607 607 > # If anything to commit, commit it right after the first key listing used
608 608 > # during lookup. This makes the commit appear before the actual getbundle
609 609 > # call.
610 610 > listkeys.makecommit= sh $TESTTMP/listkeys_makecommit.sh
611 611 > EOF
612 612
613 613 (new config need server restart)
614 614
615 615 $ killdaemons.py
616 616 $ hg serve -R ../pull-race -p $HGPORT -d --pid-file=../pull-race.pid -E main-error.log
617 617 $ cat ../pull-race.pid >> $DAEMON_PIDS
618 618
619 619 $ hg -R $TESTTMP/pull-race book
620 620 @ 1:0d2164f0ce0d
621 621 X 1:0d2164f0ce0d
622 622 * Y 5:35d1ef0a8d1b
623 623 Z 1:0d2164f0ce0d
624 624 $ hg update -r Y
625 625 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
626 626 (activating bookmark Y)
627 627 $ hg pull -B .
628 628 pulling from http://localhost:$HGPORT/
629 629 searching for changes
630 630 adding changesets
631 631 adding manifests
632 632 adding file changes
633 633 added 1 changesets with 1 changes to 1 files
634 634 updating bookmark Y
635 635 new changesets 35d1ef0a8d1b
636 636 (run 'hg update' to get a working copy)
637 637 $ hg book
638 638 @ 1:0d2164f0ce0d
639 639 X 1:0d2164f0ce0d
640 640 * Y 5:35d1ef0a8d1b
641 641 Z 1:0d2164f0ce0d
642 642
643 643 (done with this section of the test)
644 644
645 645 $ killdaemons.py
646 646 $ cd ../b
647 647
648 648 diverging a remote bookmark fails
649 649
650 650 $ hg up -q 4e3505fd9583
651 651 $ echo c4 > f2
652 652 $ hg ci -Am4
653 653 adding f2
654 654 created new head
655 655 $ echo c5 > f2
656 656 $ hg ci -Am5
657 657 $ hg log -G
658 658 @ 5:c922c0139ca0 5
659 659 |
660 660 o 4:4efff6d98829 4
661 661 |
662 662 | o 3:f6fc62dde3c0 3
663 663 |/
664 664 | o 2:0d2164f0ce0d 1
665 665 |/
666 666 | o 1:9b140be10808 2
667 667 |/
668 668 o 0:4e3505fd9583 test
669 669
670 670
671 671 $ hg book -f Y
672 672
673 673 $ cat <<EOF > ../a/.hg/hgrc
674 674 > [web]
675 675 > push_ssl = false
676 676 > allow_push = *
677 677 > EOF
678 678
679 679 $ hg serve -R ../a -p $HGPORT2 -d --pid-file=../hg2.pid
680 680 $ cat ../hg2.pid >> $DAEMON_PIDS
681 681
682 682 $ hg push http://localhost:$HGPORT2/
683 683 pushing to http://localhost:$HGPORT2/
684 684 searching for changes
685 685 abort: push creates new remote head c922c0139ca0 with bookmark 'Y'!
686 686 (merge or see 'hg help push' for details about pushing new heads)
687 687 [255]
688 688 $ hg -R ../a book
689 689 @ 1:0d2164f0ce0d
690 690 * X 1:0d2164f0ce0d
691 691 Y 3:f6fc62dde3c0
692 692 Z 1:0d2164f0ce0d
693 693
694 694
695 695 Unrelated marker does not alter the decision
696 696
697 697 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
698 698 $ hg push http://localhost:$HGPORT2/
699 699 pushing to http://localhost:$HGPORT2/
700 700 searching for changes
701 701 abort: push creates new remote head c922c0139ca0 with bookmark 'Y'!
702 702 (merge or see 'hg help push' for details about pushing new heads)
703 703 [255]
704 704 $ hg -R ../a book
705 705 @ 1:0d2164f0ce0d
706 706 * X 1:0d2164f0ce0d
707 707 Y 3:f6fc62dde3c0
708 708 Z 1:0d2164f0ce0d
709 709
710 710 Update to a successor works
711 711
712 712 $ hg id --debug -r 3
713 713 f6fc62dde3c0771e29704af56ba4d8af77abcc2f
714 714 $ hg id --debug -r 4
715 715 4efff6d98829d9c824c621afd6e3f01865f5439f
716 716 $ hg id --debug -r 5
717 717 c922c0139ca03858f655e4a2af4dd02796a63969 tip Y
718 718 $ hg debugobsolete f6fc62dde3c0771e29704af56ba4d8af77abcc2f cccccccccccccccccccccccccccccccccccccccc
719 719 obsoleted 1 changesets
720 720 $ hg debugobsolete cccccccccccccccccccccccccccccccccccccccc 4efff6d98829d9c824c621afd6e3f01865f5439f
721 721 $ hg push http://localhost:$HGPORT2/
722 722 pushing to http://localhost:$HGPORT2/
723 723 searching for changes
724 724 remote: adding changesets
725 725 remote: adding manifests
726 726 remote: adding file changes
727 727 remote: added 2 changesets with 2 changes to 1 files (+1 heads)
728 728 remote: 2 new obsolescence markers
729 729 remote: obsoleted 1 changesets
730 730 updating bookmark Y
731 731 $ hg -R ../a book
732 732 @ 1:0d2164f0ce0d
733 733 * X 1:0d2164f0ce0d
734 734 Y 5:c922c0139ca0
735 735 Z 1:0d2164f0ce0d
736 736
737 737 hgweb
738 738
739 739 $ cat <<EOF > .hg/hgrc
740 740 > [web]
741 741 > push_ssl = false
742 742 > allow_push = *
743 743 > EOF
744 744
745 745 $ hg serve -p $HGPORT -d --pid-file=../hg.pid -E errors.log
746 746 $ cat ../hg.pid >> $DAEMON_PIDS
747 747 $ cd ../a
748 748
749 749 $ hg debugpushkey http://localhost:$HGPORT/ namespaces
750 750 bookmarks
751 751 namespaces
752 752 obsolete
753 753 phases
754 754 $ hg debugpushkey http://localhost:$HGPORT/ bookmarks
755 755 @ 9b140be1080824d768c5a4691a564088eede71f9
756 756 X 9b140be1080824d768c5a4691a564088eede71f9
757 757 Y c922c0139ca03858f655e4a2af4dd02796a63969
758 758 Z 9b140be1080824d768c5a4691a564088eede71f9
759 759 foo 0000000000000000000000000000000000000000
760 760 foobar 9b140be1080824d768c5a4691a564088eede71f9
761 761 $ hg out -B http://localhost:$HGPORT/
762 762 comparing with http://localhost:$HGPORT/
763 763 searching for changed bookmarks
764 764 @ 0d2164f0ce0d
765 765 X 0d2164f0ce0d
766 766 Z 0d2164f0ce0d
767 767 foo
768 768 foobar
769 769 $ hg push -B Z http://localhost:$HGPORT/
770 770 pushing to http://localhost:$HGPORT/
771 771 searching for changes
772 772 no changes found
773 773 updating bookmark Z
774 774 [1]
775 775 $ hg book -d Z
776 776 $ hg in -B http://localhost:$HGPORT/
777 777 comparing with http://localhost:$HGPORT/
778 778 searching for changed bookmarks
779 779 @ 9b140be10808
780 780 X 9b140be10808
781 781 Z 0d2164f0ce0d
782 782 foo 000000000000
783 783 foobar 9b140be10808
784 784 $ hg pull -B Z http://localhost:$HGPORT/
785 785 pulling from http://localhost:$HGPORT/
786 786 no changes found
787 787 divergent bookmark @ stored as @1
788 788 divergent bookmark X stored as X@1
789 789 adding remote bookmark Z
790 790 adding remote bookmark foo
791 791 adding remote bookmark foobar
792 792 $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
793 793 requesting all changes
794 794 adding changesets
795 795 adding manifests
796 796 adding file changes
797 797 added 5 changesets with 5 changes to 3 files (+2 heads)
798 798 2 new obsolescence markers
799 799 new changesets 4e3505fd9583:c922c0139ca0
800 800 updating to bookmark @
801 801 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
802 802 $ hg -R cloned-bookmarks bookmarks
803 803 * @ 1:9b140be10808
804 804 X 1:9b140be10808
805 805 Y 4:c922c0139ca0
806 806 Z 2:0d2164f0ce0d
807 807 foo -1:000000000000
808 808 foobar 1:9b140be10808
809 809
810 810 $ cd ..
811 811
812 812 Test to show result of bookmarks comparison
813 813
814 814 $ mkdir bmcomparison
815 815 $ cd bmcomparison
816 816
817 817 $ hg init source
818 818 $ hg -R source debugbuilddag '+2*2*3*4'
819 819 $ hg -R source log -G --template '{rev}:{node|short}'
820 820 o 4:e7bd5218ca15
821 821 |
822 822 | o 3:6100d3090acf
823 823 |/
824 824 | o 2:fa942426a6fd
825 825 |/
826 826 | o 1:66f7d451a68b
827 827 |/
828 828 o 0:1ea73414a91b
829 829
830 830 $ hg -R source bookmarks -r 0 SAME
831 831 $ hg -R source bookmarks -r 0 ADV_ON_REPO1
832 832 $ hg -R source bookmarks -r 0 ADV_ON_REPO2
833 833 $ hg -R source bookmarks -r 0 DIFF_ADV_ON_REPO1
834 834 $ hg -R source bookmarks -r 0 DIFF_ADV_ON_REPO2
835 835 $ hg -R source bookmarks -r 1 DIVERGED
836 836
837 837 $ hg clone -U source repo1
838 838
839 839 (test that incoming/outgoing exit with 1, if there is no bookmark to
840 840 be exchanged)
841 841
842 842 $ hg -R repo1 incoming -B
843 843 comparing with $TESTTMP/bmcomparison/source
844 844 searching for changed bookmarks
845 845 no changed bookmarks found
846 846 [1]
847 847 $ hg -R repo1 outgoing -B
848 848 comparing with $TESTTMP/bmcomparison/source
849 849 searching for changed bookmarks
850 850 no changed bookmarks found
851 851 [1]
852 852
853 853 $ hg -R repo1 bookmarks -f -r 1 ADD_ON_REPO1
854 854 $ hg -R repo1 bookmarks -f -r 2 ADV_ON_REPO1
855 855 $ hg -R repo1 bookmarks -f -r 3 DIFF_ADV_ON_REPO1
856 856 $ hg -R repo1 bookmarks -f -r 3 DIFF_DIVERGED
857 857 $ hg -R repo1 -q --config extensions.mq= strip 4
858 858 $ hg -R repo1 log -G --template '{node|short} ({bookmarks})'
859 859 o 6100d3090acf (DIFF_ADV_ON_REPO1 DIFF_DIVERGED)
860 860 |
861 861 | o fa942426a6fd (ADV_ON_REPO1)
862 862 |/
863 863 | o 66f7d451a68b (ADD_ON_REPO1 DIVERGED)
864 864 |/
865 865 o 1ea73414a91b (ADV_ON_REPO2 DIFF_ADV_ON_REPO2 SAME)
866 866
867 867
868 868 $ hg clone -U source repo2
869 869 $ hg -R repo2 bookmarks -f -r 1 ADD_ON_REPO2
870 870 $ hg -R repo2 bookmarks -f -r 1 ADV_ON_REPO2
871 871 $ hg -R repo2 bookmarks -f -r 2 DIVERGED
872 872 $ hg -R repo2 bookmarks -f -r 4 DIFF_ADV_ON_REPO2
873 873 $ hg -R repo2 bookmarks -f -r 4 DIFF_DIVERGED
874 874 $ hg -R repo2 -q --config extensions.mq= strip 3
875 875 $ hg -R repo2 log -G --template '{node|short} ({bookmarks})'
876 876 o e7bd5218ca15 (DIFF_ADV_ON_REPO2 DIFF_DIVERGED)
877 877 |
878 878 | o fa942426a6fd (DIVERGED)
879 879 |/
880 880 | o 66f7d451a68b (ADD_ON_REPO2 ADV_ON_REPO2)
881 881 |/
882 882 o 1ea73414a91b (ADV_ON_REPO1 DIFF_ADV_ON_REPO1 SAME)
883 883
884 884
885 885 (test that difference of bookmarks between repositories are fully shown)
886 886
887 887 $ hg -R repo1 incoming -B repo2 -v
888 888 comparing with repo2
889 889 searching for changed bookmarks
890 890 ADD_ON_REPO2 66f7d451a68b added
891 891 ADV_ON_REPO2 66f7d451a68b advanced
892 892 DIFF_ADV_ON_REPO2 e7bd5218ca15 changed
893 893 DIFF_DIVERGED e7bd5218ca15 changed
894 894 DIVERGED fa942426a6fd diverged
895 895 $ hg -R repo1 outgoing -B repo2 -v
896 896 comparing with repo2
897 897 searching for changed bookmarks
898 898 ADD_ON_REPO1 66f7d451a68b added
899 899 ADD_ON_REPO2 deleted
900 900 ADV_ON_REPO1 fa942426a6fd advanced
901 901 DIFF_ADV_ON_REPO1 6100d3090acf advanced
902 902 DIFF_ADV_ON_REPO2 1ea73414a91b changed
903 903 DIFF_DIVERGED 6100d3090acf changed
904 904 DIVERGED 66f7d451a68b diverged
905 905
906 906 $ hg -R repo2 incoming -B repo1 -v
907 907 comparing with repo1
908 908 searching for changed bookmarks
909 909 ADD_ON_REPO1 66f7d451a68b added
910 910 ADV_ON_REPO1 fa942426a6fd advanced
911 911 DIFF_ADV_ON_REPO1 6100d3090acf changed
912 912 DIFF_DIVERGED 6100d3090acf changed
913 913 DIVERGED 66f7d451a68b diverged
914 914 $ hg -R repo2 outgoing -B repo1 -v
915 915 comparing with repo1
916 916 searching for changed bookmarks
917 917 ADD_ON_REPO1 deleted
918 918 ADD_ON_REPO2 66f7d451a68b added
919 919 ADV_ON_REPO2 66f7d451a68b advanced
920 920 DIFF_ADV_ON_REPO1 1ea73414a91b changed
921 921 DIFF_ADV_ON_REPO2 e7bd5218ca15 advanced
922 922 DIFF_DIVERGED e7bd5218ca15 changed
923 923 DIVERGED fa942426a6fd diverged
924 924
925 925 $ cd ..
926 926
927 927 Pushing a bookmark should only push the changes required by that
928 928 bookmark, not all outgoing changes:
929 929 $ hg clone http://localhost:$HGPORT/ addmarks
930 930 requesting all changes
931 931 adding changesets
932 932 adding manifests
933 933 adding file changes
934 934 added 5 changesets with 5 changes to 3 files (+2 heads)
935 935 2 new obsolescence markers
936 936 new changesets 4e3505fd9583:c922c0139ca0
937 937 updating to bookmark @
938 938 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
939 939 $ cd addmarks
940 940 $ echo foo > foo
941 941 $ hg add foo
942 942 $ hg commit -m 'add foo'
943 943 $ echo bar > bar
944 944 $ hg add bar
945 945 $ hg commit -m 'add bar'
946 946 $ hg co "tip^"
947 947 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
948 948 (leaving bookmark @)
949 949 $ hg book add-foo
950 950 $ hg book -r tip add-bar
951 951 Note: this push *must* push only a single changeset, as that's the point
952 952 of this test.
953 953 $ hg push -B add-foo --traceback
954 954 pushing to http://localhost:$HGPORT/
955 955 searching for changes
956 956 remote: adding changesets
957 957 remote: adding manifests
958 958 remote: adding file changes
959 959 remote: added 1 changesets with 1 changes to 1 files
960 960 exporting bookmark add-foo
961 961
962 962 pushing a new bookmark on a new head does not require -f if -B is specified
963 963
964 964 $ hg up -q X
965 965 $ hg book W
966 966 $ echo c5 > f2
967 967 $ hg ci -Am5
968 968 created new head
969 969 $ hg push -B .
970 970 pushing to http://localhost:$HGPORT/
971 971 searching for changes
972 972 remote: adding changesets
973 973 remote: adding manifests
974 974 remote: adding file changes
975 975 remote: added 1 changesets with 1 changes to 1 files (+1 heads)
976 976 exporting bookmark W
977 977 $ hg -R ../b id -r W
978 978 cc978a373a53 tip W
979 979
980 980 pushing an existing but divergent bookmark with -B still requires -f
981 981
982 982 $ hg clone -q . ../r
983 983 $ hg up -q X
984 984 $ echo 1 > f2
985 985 $ hg ci -qAml
986 986
987 987 $ cd ../r
988 988 $ hg up -q X
989 989 $ echo 2 > f2
990 990 $ hg ci -qAmr
991 991 $ hg push -B X
992 992 pushing to $TESTTMP/addmarks
993 993 searching for changes
994 994 remote has heads on branch 'default' that are not known locally: a2a606d9ff1b
995 995 abort: push creates new remote head 54694f811df9 with bookmark 'X'!
996 996 (pull and merge or see 'hg help push' for details about pushing new heads)
997 997 [255]
998 998 $ cd ../addmarks
999 999
1000 1000 Check summary output for incoming/outgoing bookmarks
1001 1001
1002 1002 $ hg bookmarks -d X
1003 1003 $ hg bookmarks -d Y
1004 1004 $ hg summary --remote | grep '^remote:'
1005 1005 remote: *, 2 incoming bookmarks, 1 outgoing bookmarks (glob)
1006 1006
1007 1007 $ cd ..
1008 1008
1009 1009 pushing an unchanged bookmark should result in no changes
1010 1010
1011 1011 $ hg init unchanged-a
1012 1012 $ hg init unchanged-b
1013 1013 $ cd unchanged-a
1014 1014 $ echo initial > foo
1015 1015 $ hg commit -A -m initial
1016 1016 adding foo
1017 1017 $ hg bookmark @
1018 1018 $ hg push -B @ ../unchanged-b
1019 1019 pushing to ../unchanged-b
1020 1020 searching for changes
1021 1021 adding changesets
1022 1022 adding manifests
1023 1023 adding file changes
1024 1024 added 1 changesets with 1 changes to 1 files
1025 1025 exporting bookmark @
1026 1026
1027 1027 $ hg push -B @ ../unchanged-b
1028 1028 pushing to ../unchanged-b
1029 1029 searching for changes
1030 1030 no changes found
1031 1031 [1]
1032 1032
1033 1033
1034 1034 Check hook preventing push (issue4455)
1035 1035 ======================================
1036 1036
1037 1037 $ hg bookmarks
1038 1038 * @ 0:55482a6fb4b1
1039 1039 $ hg log -G
1040 1040 @ 0:55482a6fb4b1 initial
1041 1041
1042 1042 $ hg init ../issue4455-dest
1043 1043 $ hg push ../issue4455-dest # changesets only
1044 1044 pushing to ../issue4455-dest
1045 1045 searching for changes
1046 1046 adding changesets
1047 1047 adding manifests
1048 1048 adding file changes
1049 1049 added 1 changesets with 1 changes to 1 files
1050 1050 $ cat >> .hg/hgrc << EOF
1051 1051 > [paths]
1052 1052 > local=../issue4455-dest/
1053 1053 > ssh=ssh://user@dummy/issue4455-dest
1054 1054 > http=http://localhost:$HGPORT/
1055 1055 > [ui]
1056 1056 > ssh=$PYTHON "$TESTDIR/dummyssh"
1057 1057 > EOF
1058 1058 $ cat >> ../issue4455-dest/.hg/hgrc << EOF
1059 1059 > [hooks]
1060 1060 > prepushkey=false
1061 1061 > [web]
1062 1062 > push_ssl = false
1063 1063 > allow_push = *
1064 1064 > EOF
1065 1065 $ killdaemons.py
1066 1066 $ hg serve -R ../issue4455-dest -p $HGPORT -d --pid-file=../issue4455.pid -E ../issue4455-error.log
1067 1067 $ cat ../issue4455.pid >> $DAEMON_PIDS
1068 1068
1069 1069 Local push
1070 1070 ----------
1071 1071
1072 1072 #if b2-pushkey
1073 1073
1074 1074 $ hg push -B @ local
1075 1075 pushing to $TESTTMP/issue4455-dest
1076 1076 searching for changes
1077 1077 no changes found
1078 1078 pushkey-abort: prepushkey hook exited with status 1
1079 1079 abort: exporting bookmark @ failed!
1080 1080 [255]
1081 1081
1082 1082 #endif
1083 1083 #if b2-binary
1084 1084
1085 1085 $ hg push -B @ local
1086 1086 pushing to $TESTTMP/issue4455-dest
1087 1087 searching for changes
1088 1088 no changes found
1089 1089 abort: prepushkey hook exited with status 1
1090 1090 [255]
1091 1091
1092 1092 #endif
1093 1093
1094 1094 $ hg -R ../issue4455-dest/ bookmarks
1095 1095 no bookmarks set
1096 1096
1097 1097 Using ssh
1098 1098 ---------
1099 1099
1100 1100 #if b2-pushkey
1101 1101
1102 1102 $ hg push -B @ ssh # bundle2+
1103 1103 pushing to ssh://user@dummy/issue4455-dest
1104 1104 searching for changes
1105 1105 no changes found
1106 1106 remote: pushkey-abort: prepushkey hook exited with status 1
1107 1107 abort: exporting bookmark @ failed!
1108 1108 [255]
1109 1109
1110 1110 $ hg -R ../issue4455-dest/ bookmarks
1111 1111 no bookmarks set
1112 1112
1113 1113 $ hg push -B @ ssh --config devel.legacy.exchange=bundle1
1114 1114 pushing to ssh://user@dummy/issue4455-dest
1115 1115 searching for changes
1116 1116 no changes found
1117 1117 remote: pushkey-abort: prepushkey hook exited with status 1
1118 1118 exporting bookmark @ failed!
1119 1119 [1]
1120 1120
1121 1121 #endif
1122 1122 #if b2-binary
1123 1123
1124 1124 $ hg push -B @ ssh # bundle2+
1125 1125 pushing to ssh://user@dummy/issue4455-dest
1126 1126 searching for changes
1127 1127 no changes found
1128 1128 remote: prepushkey hook exited with status 1
1129 1129 abort: push failed on remote
1130 1130 [255]
1131 1131
1132 1132 #endif
1133 1133
1134 1134 $ hg -R ../issue4455-dest/ bookmarks
1135 1135 no bookmarks set
1136 1136
1137 1137 Using http
1138 1138 ----------
1139 1139
1140 1140 #if b2-pushkey
1141 1141 $ hg push -B @ http # bundle2+
1142 1142 pushing to http://localhost:$HGPORT/
1143 1143 searching for changes
1144 1144 no changes found
1145 1145 remote: pushkey-abort: prepushkey hook exited with status 1
1146 1146 abort: exporting bookmark @ failed!
1147 1147 [255]
1148 1148
1149 1149 $ hg -R ../issue4455-dest/ bookmarks
1150 1150 no bookmarks set
1151 1151
1152 1152 $ hg push -B @ http --config devel.legacy.exchange=bundle1
1153 1153 pushing to http://localhost:$HGPORT/
1154 1154 searching for changes
1155 1155 no changes found
1156 1156 remote: pushkey-abort: prepushkey hook exited with status 1
1157 1157 exporting bookmark @ failed!
1158 1158 [1]
1159 1159
1160 1160 #endif
1161 1161
1162 1162 #if b2-binary
1163 1163
1164 1164 $ hg push -B @ ssh # bundle2+
1165 1165 pushing to ssh://user@dummy/issue4455-dest
1166 1166 searching for changes
1167 1167 no changes found
1168 1168 remote: prepushkey hook exited with status 1
1169 1169 abort: push failed on remote
1170 1170 [255]
1171 1171
1172 1172 #endif
1173 1173
1174 1174 $ hg -R ../issue4455-dest/ bookmarks
1175 1175 no bookmarks set
1176
1177 $ cd ..
1178
1179 Test that pre-pushkey compat for bookmark works as expected (issue5777)
1180
1181 $ cat << EOF >> $HGRCPATH
1182 > [ui]
1183 > ssh="$PYTHON" "$TESTDIR/dummyssh"
1184 > [server]
1185 > bookmarks-pushkey-compat = yes
1186 > EOF
1187
1188 $ hg init server
1189 $ echo foo > server/a
1190 $ hg -R server book foo
1191 $ hg -R server commit -Am a
1192 adding a
1193 $ hg clone ssh://user@dummy/server client
1194 requesting all changes
1195 adding changesets
1196 adding manifests
1197 adding file changes
1198 added 1 changesets with 1 changes to 1 files
1199 new changesets 79513d0d7716
1200 updating to branch default
1201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1202
1203 Forbid bookmark move on the server
1204
1205 $ cat << EOF >> server/.hg/hgrc
1206 > [hooks]
1207 > prepushkey.no-bm-move= echo \$HG_NAMESPACE | grep -v bookmarks
1208 > EOF
1209
1210 pushing changeset is okay
1211
1212 $ echo bar >> client/a
1213 $ hg -R client commit -m b
1214 $ hg -R client push
1215 pushing to ssh://user@dummy/server
1216 searching for changes
1217 remote: adding changesets
1218 remote: adding manifests
1219 remote: adding file changes
1220 remote: added 1 changesets with 1 changes to 1 files
1221
1222 attempt to move the bookmark is rejected
1223
1224 $ hg -R client book foo -r .
1225 moving bookmark 'foo' forward from 79513d0d7716
1226
1227 #if b2-pushkey
1228 $ hg -R client push
1229 pushing to ssh://user@dummy/server
1230 searching for changes
1231 no changes found
1232 remote: pushkey-abort: prepushkey.no-bm-move hook exited with status 1
1233 abort: updating bookmark foo failed!
1234 [255]
1235 #endif
1236 #if b2-binary
1237 $ hg -R client push
1238 pushing to ssh://user@dummy/server
1239 searching for changes
1240 no changes found
1241 remote: prepushkey.no-bm-move hook exited with status 1
1242 abort: push failed on remote
1243 [255]
1244 #endif
@@ -1,1148 +1,1148
1 1 Test exchange of common information using bundle2
2 2
3 3
4 4 $ getmainid() {
5 5 > hg -R main log --template '{node}\n' --rev "$1"
6 6 > }
7 7
8 8 enable obsolescence
9 9
10 10 $ cp $HGRCPATH $TESTTMP/hgrc.orig
11 11 $ cat > $TESTTMP/bundle2-pushkey-hook.sh << EOF
12 12 > echo pushkey: lock state after \"\$HG_NAMESPACE\"
13 13 > hg debuglock
14 14 > EOF
15 15
16 16 $ cat >> $HGRCPATH << EOF
17 17 > [experimental]
18 18 > evolution.createmarkers=True
19 19 > evolution.exchange=True
20 20 > bundle2-output-capture=True
21 21 > [ui]
22 22 > ssh="$PYTHON" "$TESTDIR/dummyssh"
23 23 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
24 24 > [web]
25 25 > push_ssl = false
26 26 > allow_push = *
27 27 > [phases]
28 28 > publish=False
29 29 > [hooks]
30 30 > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
31 31 > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
32 32 > txnclose.env = sh -c "HG_LOCAL= printenv.py txnclose"
33 33 > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
34 34 > EOF
35 35
36 36 The extension requires a repo (currently unused)
37 37
38 38 $ hg init main
39 39 $ cd main
40 40 $ touch a
41 41 $ hg add a
42 42 $ hg commit -m 'a'
43 43 pre-close-tip:3903775176ed draft
44 44 postclose-tip:3903775176ed draft
45 45 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
46 46
47 47 $ hg unbundle $TESTDIR/bundles/rebase.hg
48 48 adding changesets
49 49 adding manifests
50 50 adding file changes
51 51 added 8 changesets with 7 changes to 7 files (+3 heads)
52 52 pre-close-tip:02de42196ebe draft
53 53 new changesets cd010b8cd998:02de42196ebe
54 54 postclose-tip:02de42196ebe draft
55 55 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:$ID$ HG_TXNNAME=unbundle
56 56 bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
57 57 (run 'hg heads' to see heads, 'hg merge' to merge)
58 58
59 59 $ cd ..
60 60
61 61 Real world exchange
62 62 =====================
63 63
64 64 Add more obsolescence information
65 65
66 66 $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
67 67 pre-close-tip:02de42196ebe draft
68 68 postclose-tip:02de42196ebe draft
69 69 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
70 70 $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
71 71 pre-close-tip:02de42196ebe draft
72 72 postclose-tip:02de42196ebe draft
73 73 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
74 74
75 75 clone --pull
76 76
77 77 $ hg -R main phase --public cd010b8cd998
78 78 pre-close-tip:02de42196ebe draft
79 79 postclose-tip:02de42196ebe draft
80 80 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=phase
81 81 $ hg clone main other --pull --rev 9520eea781bc
82 82 adding changesets
83 83 adding manifests
84 84 adding file changes
85 85 added 2 changesets with 2 changes to 2 files
86 86 1 new obsolescence markers
87 87 pre-close-tip:9520eea781bc draft
88 88 new changesets cd010b8cd998:9520eea781bc
89 89 postclose-tip:9520eea781bc draft
90 90 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
91 91 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
92 92 updating to branch default
93 93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 94 $ hg -R other log -G
95 95 @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
96 96 |
97 97 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
98 98
99 99 $ hg -R other debugobsolete
100 100 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
101 101
102 102 pull
103 103
104 104 $ hg -R main phase --public 9520eea781bc
105 105 pre-close-tip:02de42196ebe draft
106 106 postclose-tip:02de42196ebe draft
107 107 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=phase
108 108 $ hg -R other pull -r 24b6387c8c8c
109 109 pulling from $TESTTMP/main
110 110 searching for changes
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 1 changesets with 1 changes to 1 files (+1 heads)
115 115 1 new obsolescence markers
116 116 pre-close-tip:24b6387c8c8c draft
117 117 new changesets 24b6387c8c8c
118 118 postclose-tip:24b6387c8c8c draft
119 119 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
120 120 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
121 121 (run 'hg heads' to see heads, 'hg merge' to merge)
122 122 $ hg -R other log -G
123 123 o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
124 124 |
125 125 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
126 126 |/
127 127 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
128 128
129 129 $ hg -R other debugobsolete
130 130 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
131 131 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
132 132
133 133 pull empty (with phase movement)
134 134
135 135 $ hg -R main phase --public 24b6387c8c8c
136 136 pre-close-tip:02de42196ebe draft
137 137 postclose-tip:02de42196ebe draft
138 138 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=phase
139 139 $ hg -R other pull -r 24b6387c8c8c
140 140 pulling from $TESTTMP/main
141 141 no changes found
142 142 pre-close-tip:24b6387c8c8c public
143 143 postclose-tip:24b6387c8c8c public
144 144 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
145 145 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
146 146 $ hg -R other log -G
147 147 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
148 148 |
149 149 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
150 150 |/
151 151 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
152 152
153 153 $ hg -R other debugobsolete
154 154 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
155 155 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
156 156
157 157 pull empty
158 158
159 159 $ hg -R other pull -r 24b6387c8c8c
160 160 pulling from $TESTTMP/main
161 161 no changes found
162 162 pre-close-tip:24b6387c8c8c public
163 163 postclose-tip:24b6387c8c8c public
164 164 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
165 165 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
166 166 $ hg -R other log -G
167 167 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
168 168 |
169 169 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
170 170 |/
171 171 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
172 172
173 173 $ hg -R other debugobsolete
174 174 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
175 175 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
176 176
177 177 add extra data to test their exchange during push
178 178
179 179 $ hg -R main bookmark --rev eea13746799a book_eea1
180 180 pre-close-tip:02de42196ebe draft
181 181 postclose-tip:02de42196ebe draft
182 182 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
183 183 $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
184 184 pre-close-tip:02de42196ebe draft
185 185 postclose-tip:02de42196ebe draft
186 186 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
187 187 $ hg -R main bookmark --rev 02de42196ebe book_02de
188 188 pre-close-tip:02de42196ebe draft book_02de
189 189 postclose-tip:02de42196ebe draft book_02de
190 190 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
191 191 $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
192 192 pre-close-tip:02de42196ebe draft book_02de
193 193 postclose-tip:02de42196ebe draft book_02de
194 194 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
195 195 $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
196 196 pre-close-tip:02de42196ebe draft book_02de
197 197 postclose-tip:02de42196ebe draft book_02de
198 198 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
199 199 $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
200 200 pre-close-tip:02de42196ebe draft book_02de
201 201 postclose-tip:02de42196ebe draft book_02de
202 202 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
203 203 $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
204 204 pre-close-tip:02de42196ebe draft book_02de
205 205 postclose-tip:02de42196ebe draft book_02de
206 206 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
207 207 $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
208 208 pre-close-tip:02de42196ebe draft book_02de
209 209 postclose-tip:02de42196ebe draft book_02de
210 210 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
211 211 $ hg -R main bookmark --rev 32af7686d403 book_32af
212 212 pre-close-tip:02de42196ebe draft book_02de
213 213 postclose-tip:02de42196ebe draft book_02de
214 214 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
215 215 $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
216 216 pre-close-tip:02de42196ebe draft book_02de
217 217 postclose-tip:02de42196ebe draft book_02de
218 218 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=debugobsolete
219 219
220 220 $ hg -R other bookmark --rev cd010b8cd998 book_eea1
221 221 pre-close-tip:24b6387c8c8c public
222 222 postclose-tip:24b6387c8c8c public
223 223 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
224 224 $ hg -R other bookmark --rev cd010b8cd998 book_02de
225 225 pre-close-tip:24b6387c8c8c public
226 226 postclose-tip:24b6387c8c8c public
227 227 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
228 228 $ hg -R other bookmark --rev cd010b8cd998 book_42cc
229 229 pre-close-tip:24b6387c8c8c public
230 230 postclose-tip:24b6387c8c8c public
231 231 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
232 232 $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
233 233 pre-close-tip:24b6387c8c8c public
234 234 postclose-tip:24b6387c8c8c public
235 235 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
236 236 $ hg -R other bookmark --rev cd010b8cd998 book_32af
237 237 pre-close-tip:24b6387c8c8c public
238 238 postclose-tip:24b6387c8c8c public
239 239 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
240 240
241 241 $ hg -R main phase --public eea13746799a
242 242 pre-close-tip:02de42196ebe draft book_02de
243 243 postclose-tip:02de42196ebe draft book_02de
244 244 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=phase
245 245
246 246 push
247 247 $ hg -R main push other --rev eea13746799a --bookmark book_eea1
248 248 pushing to other
249 249 searching for changes
250 250 remote: adding changesets
251 251 remote: adding manifests
252 252 remote: adding file changes
253 253 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
254 254 remote: 1 new obsolescence markers
255 255 remote: pre-close-tip:eea13746799a public book_eea1
256 remote: pushkey: lock state after "bookmark"
256 remote: pushkey: lock state after "bookmarks"
257 257 remote: lock: free
258 258 remote: wlock: free
259 259 remote: postclose-tip:eea13746799a public book_eea1
260 260 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_NODE_LAST=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/other
261 261 updating bookmark book_eea1
262 262 pre-close-tip:02de42196ebe draft book_02de
263 263 postclose-tip:02de42196ebe draft book_02de
264 264 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_SOURCE=push-response HG_TXNID=TXN:$ID$ HG_TXNNAME=push-response
265 265 file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
266 266 $ hg -R other log -G
267 267 o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
268 268 |\
269 269 | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
270 270 | |
271 271 @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
272 272 |/
273 273 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
274 274
275 275 $ hg -R other debugobsolete
276 276 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
277 277 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
278 278 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
279 279
280 280 pull over ssh
281 281
282 282 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
283 283 pulling from ssh://user@dummy/main
284 284 searching for changes
285 285 adding changesets
286 286 adding manifests
287 287 adding file changes
288 288 added 1 changesets with 1 changes to 1 files (+1 heads)
289 289 1 new obsolescence markers
290 290 updating bookmark book_02de
291 291 pre-close-tip:02de42196ebe draft book_02de
292 292 new changesets 02de42196ebe
293 293 postclose-tip:02de42196ebe draft book_02de
294 294 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
295 295 ssh://user@dummy/main HG_URL=ssh://user@dummy/main
296 296 (run 'hg heads' to see heads, 'hg merge' to merge)
297 297 $ hg -R other debugobsolete
298 298 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
299 299 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
300 300 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
301 301 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
302 302
303 303 pull over http
304 304
305 305 $ hg serve -R main -p $HGPORT -d --pid-file=main.pid -E main-error.log
306 306 $ cat main.pid >> $DAEMON_PIDS
307 307
308 308 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
309 309 pulling from http://localhost:$HGPORT/
310 310 searching for changes
311 311 adding changesets
312 312 adding manifests
313 313 adding file changes
314 314 added 1 changesets with 1 changes to 1 files (+1 heads)
315 315 1 new obsolescence markers
316 316 updating bookmark book_42cc
317 317 pre-close-tip:42ccdea3bb16 draft book_42cc
318 318 new changesets 42ccdea3bb16
319 319 postclose-tip:42ccdea3bb16 draft book_42cc
320 320 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
321 321 http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
322 322 (run 'hg heads .' to see heads, 'hg merge' to merge)
323 323 $ cat main-error.log
324 324 $ hg -R other debugobsolete
325 325 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
326 326 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
327 327 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
328 328 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
329 329 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
330 330
331 331 push over ssh
332 332
333 333 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
334 334 pushing to ssh://user@dummy/other
335 335 searching for changes
336 336 remote: adding changesets
337 337 remote: adding manifests
338 338 remote: adding file changes
339 339 remote: added 1 changesets with 1 changes to 1 files
340 340 remote: 1 new obsolescence markers
341 341 remote: pre-close-tip:5fddd98957c8 draft book_5fdd
342 remote: pushkey: lock state after "bookmark"
342 remote: pushkey: lock state after "bookmarks"
343 343 remote: lock: free
344 344 remote: wlock: free
345 345 remote: postclose-tip:5fddd98957c8 draft book_5fdd
346 346 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_NODE_LAST=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_TXNNAME=serve HG_URL=remote:ssh:$LOCALIP
347 347 updating bookmark book_5fdd
348 348 pre-close-tip:02de42196ebe draft book_02de
349 349 postclose-tip:02de42196ebe draft book_02de
350 350 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_SOURCE=push-response HG_TXNID=TXN:$ID$ HG_TXNNAME=push-response
351 351 ssh://user@dummy/other HG_URL=ssh://user@dummy/other
352 352 $ hg -R other log -G
353 353 o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
354 354 |
355 355 o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
356 356 |
357 357 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
358 358 | |
359 359 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
360 360 | |/|
361 361 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
362 362 |/ /
363 363 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
364 364 |/
365 365 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
366 366
367 367 $ hg -R other debugobsolete
368 368 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
369 369 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
370 370 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
371 371 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
372 372 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
373 373 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
374 374
375 375 push over http
376 376
377 377 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
378 378 $ cat other.pid >> $DAEMON_PIDS
379 379
380 380 $ hg -R main phase --public 32af7686d403
381 381 pre-close-tip:02de42196ebe draft book_02de
382 382 postclose-tip:02de42196ebe draft book_02de
383 383 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=phase
384 384 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
385 385 pushing to http://localhost:$HGPORT2/
386 386 searching for changes
387 387 remote: adding changesets
388 388 remote: adding manifests
389 389 remote: adding file changes
390 390 remote: added 1 changesets with 1 changes to 1 files
391 391 remote: 1 new obsolescence markers
392 392 remote: pre-close-tip:32af7686d403 public book_32af
393 remote: pushkey: lock state after "bookmark"
393 remote: pushkey: lock state after "bookmarks"
394 394 remote: lock: free
395 395 remote: wlock: free
396 396 remote: postclose-tip:32af7686d403 public book_32af
397 397 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=32af7686d403cf45b5d95f2d70cebea587ac806a HG_NODE_LAST=32af7686d403cf45b5d95f2d70cebea587ac806a HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_TXNNAME=serve HG_URL=remote:http:$LOCALIP: (glob)
398 398 updating bookmark book_32af
399 399 pre-close-tip:02de42196ebe draft book_02de
400 400 postclose-tip:02de42196ebe draft book_02de
401 401 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_SOURCE=push-response HG_TXNID=TXN:$ID$ HG_TXNNAME=push-response
402 402 http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
403 403 $ cat other-error.log
404 404
405 405 Check final content.
406 406
407 407 $ hg -R other log -G
408 408 o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
409 409 |
410 410 o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
411 411 |
412 412 o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
413 413 |
414 414 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
415 415 | |
416 416 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
417 417 | |/|
418 418 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
419 419 |/ /
420 420 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
421 421 |/
422 422 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
423 423
424 424 $ hg -R other debugobsolete
425 425 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
426 426 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
427 427 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
428 428 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
429 429 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
430 430 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
431 431 7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
432 432
433 433 (check that no 'pending' files remain)
434 434
435 435 $ ls -1 other/.hg/bookmarks*
436 436 other/.hg/bookmarks
437 437 $ ls -1 other/.hg/store/phaseroots*
438 438 other/.hg/store/phaseroots
439 439 $ ls -1 other/.hg/store/00changelog.i*
440 440 other/.hg/store/00changelog.i
441 441
442 442 Error Handling
443 443 ==============
444 444
445 445 Check that errors are properly returned to the client during push.
446 446
447 447 Setting up
448 448
449 449 $ cat > failpush.py << EOF
450 450 > """A small extension that makes push fails when using bundle2
451 451 >
452 452 > used to test error handling in bundle2
453 453 > """
454 454 >
455 455 > from mercurial import error
456 456 > from mercurial import bundle2
457 457 > from mercurial import exchange
458 458 > from mercurial import extensions
459 459 > from mercurial import registrar
460 460 > cmdtable = {}
461 461 > command = registrar.command(cmdtable)
462 462 >
463 463 > configtable = {}
464 464 > configitem = registrar.configitem(configtable)
465 465 > configitem('failpush', 'reason',
466 466 > default=None,
467 467 > )
468 468 >
469 469 > def _pushbundle2failpart(pushop, bundler):
470 470 > reason = pushop.ui.config('failpush', 'reason')
471 471 > part = None
472 472 > if reason == 'abort':
473 473 > bundler.newpart('test:abort')
474 474 > if reason == 'unknown':
475 475 > bundler.newpart('test:unknown')
476 476 > if reason == 'race':
477 477 > # 20 Bytes of crap
478 478 > bundler.newpart('check:heads', data='01234567890123456789')
479 479 >
480 480 > @bundle2.parthandler("test:abort")
481 481 > def handleabort(op, part):
482 482 > raise error.Abort('Abandon ship!', hint="don't panic")
483 483 >
484 484 > def uisetup(ui):
485 485 > exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
486 486 > exchange.b2partsgenorder.insert(0, 'failpart')
487 487 >
488 488 > EOF
489 489
490 490 $ cd main
491 491 $ hg up tip
492 492 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
493 493 $ echo 'I' > I
494 494 $ hg add I
495 495 $ hg ci -m 'I'
496 496 pre-close-tip:e7ec4e813ba6 draft
497 497 postclose-tip:e7ec4e813ba6 draft
498 498 txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
499 499 $ hg id
500 500 e7ec4e813ba6 tip
501 501 $ cd ..
502 502
503 503 $ cat << EOF >> $HGRCPATH
504 504 > [extensions]
505 505 > failpush=$TESTTMP/failpush.py
506 506 > EOF
507 507
508 508 $ killdaemons.py
509 509 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
510 510 $ cat other.pid >> $DAEMON_PIDS
511 511
512 512 Doing the actual push: Abort error
513 513
514 514 $ cat << EOF >> $HGRCPATH
515 515 > [failpush]
516 516 > reason = abort
517 517 > EOF
518 518
519 519 $ hg -R main push other -r e7ec4e813ba6
520 520 pushing to other
521 521 searching for changes
522 522 abort: Abandon ship!
523 523 (don't panic)
524 524 [255]
525 525
526 526 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
527 527 pushing to ssh://user@dummy/other
528 528 searching for changes
529 529 remote: Abandon ship!
530 530 remote: (don't panic)
531 531 abort: push failed on remote
532 532 [255]
533 533
534 534 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
535 535 pushing to http://localhost:$HGPORT2/
536 536 searching for changes
537 537 remote: Abandon ship!
538 538 remote: (don't panic)
539 539 abort: push failed on remote
540 540 [255]
541 541
542 542
543 543 Doing the actual push: unknown mandatory parts
544 544
545 545 $ cat << EOF >> $HGRCPATH
546 546 > [failpush]
547 547 > reason = unknown
548 548 > EOF
549 549
550 550 $ hg -R main push other -r e7ec4e813ba6
551 551 pushing to other
552 552 searching for changes
553 553 abort: missing support for test:unknown
554 554 [255]
555 555
556 556 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
557 557 pushing to ssh://user@dummy/other
558 558 searching for changes
559 559 abort: missing support for test:unknown
560 560 [255]
561 561
562 562 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
563 563 pushing to http://localhost:$HGPORT2/
564 564 searching for changes
565 565 abort: missing support for test:unknown
566 566 [255]
567 567
568 568 Doing the actual push: race
569 569
570 570 $ cat << EOF >> $HGRCPATH
571 571 > [failpush]
572 572 > reason = race
573 573 > EOF
574 574
575 575 $ hg -R main push other -r e7ec4e813ba6
576 576 pushing to other
577 577 searching for changes
578 578 abort: push failed:
579 579 'repository changed while pushing - please try again'
580 580 [255]
581 581
582 582 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
583 583 pushing to ssh://user@dummy/other
584 584 searching for changes
585 585 abort: push failed:
586 586 'repository changed while pushing - please try again'
587 587 [255]
588 588
589 589 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
590 590 pushing to http://localhost:$HGPORT2/
591 591 searching for changes
592 592 abort: push failed:
593 593 'repository changed while pushing - please try again'
594 594 [255]
595 595
596 596 Doing the actual push: hook abort
597 597
598 598 $ cat << EOF >> $HGRCPATH
599 599 > [failpush]
600 600 > reason =
601 601 > [hooks]
602 602 > pretxnclose.failpush = sh -c "echo 'You shall not pass!'; false"
603 603 > txnabort.failpush = sh -c "echo 'Cleaning up the mess...'"
604 604 > EOF
605 605
606 606 $ killdaemons.py
607 607 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
608 608 $ cat other.pid >> $DAEMON_PIDS
609 609
610 610 $ hg -R main push other -r e7ec4e813ba6
611 611 pushing to other
612 612 searching for changes
613 613 remote: adding changesets
614 614 remote: adding manifests
615 615 remote: adding file changes
616 616 remote: added 1 changesets with 1 changes to 1 files
617 617 remote: pre-close-tip:e7ec4e813ba6 draft
618 618 remote: You shall not pass!
619 619 remote: transaction abort!
620 620 remote: Cleaning up the mess...
621 621 remote: rollback completed
622 622 abort: pretxnclose.failpush hook exited with status 1
623 623 [255]
624 624
625 625 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
626 626 pushing to ssh://user@dummy/other
627 627 searching for changes
628 628 remote: adding changesets
629 629 remote: adding manifests
630 630 remote: adding file changes
631 631 remote: added 1 changesets with 1 changes to 1 files
632 632 remote: pre-close-tip:e7ec4e813ba6 draft
633 633 remote: You shall not pass!
634 634 remote: transaction abort!
635 635 remote: Cleaning up the mess...
636 636 remote: rollback completed
637 637 remote: pretxnclose.failpush hook exited with status 1
638 638 abort: push failed on remote
639 639 [255]
640 640
641 641 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
642 642 pushing to http://localhost:$HGPORT2/
643 643 searching for changes
644 644 remote: adding changesets
645 645 remote: adding manifests
646 646 remote: adding file changes
647 647 remote: added 1 changesets with 1 changes to 1 files
648 648 remote: pre-close-tip:e7ec4e813ba6 draft
649 649 remote: You shall not pass!
650 650 remote: transaction abort!
651 651 remote: Cleaning up the mess...
652 652 remote: rollback completed
653 653 remote: pretxnclose.failpush hook exited with status 1
654 654 abort: push failed on remote
655 655 [255]
656 656
657 657 (check that no 'pending' files remain)
658 658
659 659 $ ls -1 other/.hg/bookmarks*
660 660 other/.hg/bookmarks
661 661 $ ls -1 other/.hg/store/phaseroots*
662 662 other/.hg/store/phaseroots
663 663 $ ls -1 other/.hg/store/00changelog.i*
664 664 other/.hg/store/00changelog.i
665 665
666 666 Check error from hook during the unbundling process itself
667 667
668 668 $ cat << EOF >> $HGRCPATH
669 669 > pretxnchangegroup = sh -c "echo 'Fail early!'; false"
670 670 > EOF
671 671 $ killdaemons.py # reload http config
672 672 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
673 673 $ cat other.pid >> $DAEMON_PIDS
674 674
675 675 $ hg -R main push other -r e7ec4e813ba6
676 676 pushing to other
677 677 searching for changes
678 678 remote: adding changesets
679 679 remote: adding manifests
680 680 remote: adding file changes
681 681 remote: added 1 changesets with 1 changes to 1 files
682 682 remote: Fail early!
683 683 remote: transaction abort!
684 684 remote: Cleaning up the mess...
685 685 remote: rollback completed
686 686 abort: pretxnchangegroup hook exited with status 1
687 687 [255]
688 688 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
689 689 pushing to ssh://user@dummy/other
690 690 searching for changes
691 691 remote: adding changesets
692 692 remote: adding manifests
693 693 remote: adding file changes
694 694 remote: added 1 changesets with 1 changes to 1 files
695 695 remote: Fail early!
696 696 remote: transaction abort!
697 697 remote: Cleaning up the mess...
698 698 remote: rollback completed
699 699 remote: pretxnchangegroup hook exited with status 1
700 700 abort: push failed on remote
701 701 [255]
702 702 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
703 703 pushing to http://localhost:$HGPORT2/
704 704 searching for changes
705 705 remote: adding changesets
706 706 remote: adding manifests
707 707 remote: adding file changes
708 708 remote: added 1 changesets with 1 changes to 1 files
709 709 remote: Fail early!
710 710 remote: transaction abort!
711 711 remote: Cleaning up the mess...
712 712 remote: rollback completed
713 713 remote: pretxnchangegroup hook exited with status 1
714 714 abort: push failed on remote
715 715 [255]
716 716
717 717 Check output capture control.
718 718
719 719 (should be still forced for http, disabled for local and ssh)
720 720
721 721 $ cat >> $HGRCPATH << EOF
722 722 > [experimental]
723 723 > bundle2-output-capture=False
724 724 > EOF
725 725
726 726 $ hg -R main push other -r e7ec4e813ba6
727 727 pushing to other
728 728 searching for changes
729 729 adding changesets
730 730 adding manifests
731 731 adding file changes
732 732 added 1 changesets with 1 changes to 1 files
733 733 Fail early!
734 734 transaction abort!
735 735 Cleaning up the mess...
736 736 rollback completed
737 737 abort: pretxnchangegroup hook exited with status 1
738 738 [255]
739 739 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
740 740 pushing to ssh://user@dummy/other
741 741 searching for changes
742 742 remote: adding changesets
743 743 remote: adding manifests
744 744 remote: adding file changes
745 745 remote: added 1 changesets with 1 changes to 1 files
746 746 remote: Fail early!
747 747 remote: transaction abort!
748 748 remote: Cleaning up the mess...
749 749 remote: rollback completed
750 750 remote: pretxnchangegroup hook exited with status 1
751 751 abort: push failed on remote
752 752 [255]
753 753 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
754 754 pushing to http://localhost:$HGPORT2/
755 755 searching for changes
756 756 remote: adding changesets
757 757 remote: adding manifests
758 758 remote: adding file changes
759 759 remote: added 1 changesets with 1 changes to 1 files
760 760 remote: Fail early!
761 761 remote: transaction abort!
762 762 remote: Cleaning up the mess...
763 763 remote: rollback completed
764 764 remote: pretxnchangegroup hook exited with status 1
765 765 abort: push failed on remote
766 766 [255]
767 767
768 768 Check abort from mandatory pushkey
769 769
770 770 $ cat > mandatorypart.py << EOF
771 771 > from mercurial import exchange
772 772 > from mercurial import pushkey
773 773 > from mercurial import node
774 774 > from mercurial import error
775 775 > @exchange.b2partsgenerator('failingpuskey')
776 776 > def addfailingpushey(pushop, bundler):
777 777 > enc = pushkey.encode
778 778 > part = bundler.newpart('pushkey')
779 779 > part.addparam('namespace', enc('phases'))
780 780 > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
781 781 > part.addparam('old', enc(str(0))) # successful update
782 782 > part.addparam('new', enc(str(0)))
783 783 > def fail(pushop, exc):
784 784 > raise error.Abort('Correct phase push failed (because hooks)')
785 785 > pushop.pkfailcb[part.id] = fail
786 786 > EOF
787 787 $ cat >> $HGRCPATH << EOF
788 788 > [hooks]
789 789 > pretxnchangegroup=
790 790 > pretxnclose.failpush=
791 791 > prepushkey.failpush = sh -c "echo 'do not push the key !'; false"
792 792 > [extensions]
793 793 > mandatorypart=$TESTTMP/mandatorypart.py
794 794 > EOF
795 795 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
796 796 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
797 797 $ cat other.pid >> $DAEMON_PIDS
798 798
799 799 (Failure from a hook)
800 800
801 801 $ hg -R main push other -r e7ec4e813ba6
802 802 pushing to other
803 803 searching for changes
804 804 adding changesets
805 805 adding manifests
806 806 adding file changes
807 807 added 1 changesets with 1 changes to 1 files
808 808 do not push the key !
809 809 pushkey-abort: prepushkey.failpush hook exited with status 1
810 810 transaction abort!
811 811 Cleaning up the mess...
812 812 rollback completed
813 813 abort: Correct phase push failed (because hooks)
814 814 [255]
815 815 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
816 816 pushing to ssh://user@dummy/other
817 817 searching for changes
818 818 remote: adding changesets
819 819 remote: adding manifests
820 820 remote: adding file changes
821 821 remote: added 1 changesets with 1 changes to 1 files
822 822 remote: do not push the key !
823 823 remote: pushkey-abort: prepushkey.failpush hook exited with status 1
824 824 remote: transaction abort!
825 825 remote: Cleaning up the mess...
826 826 remote: rollback completed
827 827 abort: Correct phase push failed (because hooks)
828 828 [255]
829 829 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
830 830 pushing to http://localhost:$HGPORT2/
831 831 searching for changes
832 832 remote: adding changesets
833 833 remote: adding manifests
834 834 remote: adding file changes
835 835 remote: added 1 changesets with 1 changes to 1 files
836 836 remote: do not push the key !
837 837 remote: pushkey-abort: prepushkey.failpush hook exited with status 1
838 838 remote: transaction abort!
839 839 remote: Cleaning up the mess...
840 840 remote: rollback completed
841 841 abort: Correct phase push failed (because hooks)
842 842 [255]
843 843
844 844 (Failure from a the pushkey)
845 845
846 846 $ cat > mandatorypart.py << EOF
847 847 > from mercurial import exchange
848 848 > from mercurial import pushkey
849 849 > from mercurial import node
850 850 > from mercurial import error
851 851 > @exchange.b2partsgenerator('failingpuskey')
852 852 > def addfailingpushey(pushop, bundler):
853 853 > enc = pushkey.encode
854 854 > part = bundler.newpart('pushkey')
855 855 > part.addparam('namespace', enc('phases'))
856 856 > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
857 857 > part.addparam('old', enc(str(4))) # will fail
858 858 > part.addparam('new', enc(str(3)))
859 859 > def fail(pushop, exc):
860 860 > raise error.Abort('Clown phase push failed')
861 861 > pushop.pkfailcb[part.id] = fail
862 862 > EOF
863 863 $ cat >> $HGRCPATH << EOF
864 864 > [hooks]
865 865 > prepushkey.failpush =
866 866 > EOF
867 867 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
868 868 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
869 869 $ cat other.pid >> $DAEMON_PIDS
870 870
871 871 $ hg -R main push other -r e7ec4e813ba6
872 872 pushing to other
873 873 searching for changes
874 874 adding changesets
875 875 adding manifests
876 876 adding file changes
877 877 added 1 changesets with 1 changes to 1 files
878 878 transaction abort!
879 879 Cleaning up the mess...
880 880 rollback completed
881 881 pushkey: lock state after "phases"
882 882 lock: free
883 883 wlock: free
884 884 abort: Clown phase push failed
885 885 [255]
886 886 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
887 887 pushing to ssh://user@dummy/other
888 888 searching for changes
889 889 remote: adding changesets
890 890 remote: adding manifests
891 891 remote: adding file changes
892 892 remote: added 1 changesets with 1 changes to 1 files
893 893 remote: transaction abort!
894 894 remote: Cleaning up the mess...
895 895 remote: rollback completed
896 896 remote: pushkey: lock state after "phases"
897 897 remote: lock: free
898 898 remote: wlock: free
899 899 abort: Clown phase push failed
900 900 [255]
901 901 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
902 902 pushing to http://localhost:$HGPORT2/
903 903 searching for changes
904 904 remote: adding changesets
905 905 remote: adding manifests
906 906 remote: adding file changes
907 907 remote: added 1 changesets with 1 changes to 1 files
908 908 remote: transaction abort!
909 909 remote: Cleaning up the mess...
910 910 remote: rollback completed
911 911 remote: pushkey: lock state after "phases"
912 912 remote: lock: free
913 913 remote: wlock: free
914 914 abort: Clown phase push failed
915 915 [255]
916 916
917 917 Test lazily acquiring the lock during unbundle
918 918 $ cp $TESTTMP/hgrc.orig $HGRCPATH
919 919 $ cat >> $HGRCPATH <<EOF
920 920 > [ui]
921 921 > ssh="$PYTHON" "$TESTDIR/dummyssh"
922 922 > EOF
923 923
924 924 $ cat >> $TESTTMP/locktester.py <<EOF
925 925 > import os
926 926 > from mercurial import extensions, bundle2, util
927 927 > def checklock(orig, repo, *args, **kwargs):
928 928 > if repo.svfs.lexists("lock"):
929 929 > raise util.Abort("Lock should not be taken")
930 930 > return orig(repo, *args, **kwargs)
931 931 > def extsetup(ui):
932 932 > extensions.wrapfunction(bundle2, 'processbundle', checklock)
933 933 > EOF
934 934
935 935 $ hg init lazylock
936 936 $ cat >> lazylock/.hg/hgrc <<EOF
937 937 > [extensions]
938 938 > locktester=$TESTTMP/locktester.py
939 939 > EOF
940 940
941 941 $ hg clone -q ssh://user@dummy/lazylock lazylockclient
942 942 $ cd lazylockclient
943 943 $ touch a && hg ci -Aqm a
944 944 $ hg push
945 945 pushing to ssh://user@dummy/lazylock
946 946 searching for changes
947 947 remote: Lock should not be taken
948 948 abort: push failed on remote
949 949 [255]
950 950
951 951 $ cat >> ../lazylock/.hg/hgrc <<EOF
952 952 > [experimental]
953 953 > bundle2lazylocking=True
954 954 > EOF
955 955 $ hg push
956 956 pushing to ssh://user@dummy/lazylock
957 957 searching for changes
958 958 remote: adding changesets
959 959 remote: adding manifests
960 960 remote: adding file changes
961 961 remote: added 1 changesets with 1 changes to 1 files
962 962
963 963 $ cd ..
964 964
965 965 Servers can disable bundle1 for clone/pull operations
966 966
967 967 $ killdaemons.py
968 968 $ hg init bundle2onlyserver
969 969 $ cd bundle2onlyserver
970 970 $ cat > .hg/hgrc << EOF
971 971 > [server]
972 972 > bundle1.pull = false
973 973 > EOF
974 974
975 975 $ touch foo
976 976 $ hg -q commit -A -m initial
977 977
978 978 $ hg serve -p $HGPORT -d --pid-file=hg.pid
979 979 $ cat hg.pid >> $DAEMON_PIDS
980 980
981 981 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
982 982 requesting all changes
983 983 abort: remote error:
984 984 incompatible Mercurial client; bundle2 required
985 985 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
986 986 [255]
987 987 $ killdaemons.py
988 988 $ cd ..
989 989
990 990 bundle1 can still pull non-generaldelta repos when generaldelta bundle1 disabled
991 991
992 992 $ hg --config format.usegeneraldelta=false init notgdserver
993 993 $ cd notgdserver
994 994 $ cat > .hg/hgrc << EOF
995 995 > [server]
996 996 > bundle1gd.pull = false
997 997 > EOF
998 998
999 999 $ touch foo
1000 1000 $ hg -q commit -A -m initial
1001 1001 $ hg serve -p $HGPORT -d --pid-file=hg.pid
1002 1002 $ cat hg.pid >> $DAEMON_PIDS
1003 1003
1004 1004 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2-1
1005 1005 requesting all changes
1006 1006 adding changesets
1007 1007 adding manifests
1008 1008 adding file changes
1009 1009 added 1 changesets with 1 changes to 1 files
1010 1010 new changesets 96ee1d7354c4
1011 1011 updating to branch default
1012 1012 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1013 1013
1014 1014 $ killdaemons.py
1015 1015 $ cd ../bundle2onlyserver
1016 1016
1017 1017 bundle1 pull can be disabled for generaldelta repos only
1018 1018
1019 1019 $ cat > .hg/hgrc << EOF
1020 1020 > [server]
1021 1021 > bundle1gd.pull = false
1022 1022 > EOF
1023 1023
1024 1024 $ hg serve -p $HGPORT -d --pid-file=hg.pid
1025 1025 $ cat hg.pid >> $DAEMON_PIDS
1026 1026 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
1027 1027 requesting all changes
1028 1028 abort: remote error:
1029 1029 incompatible Mercurial client; bundle2 required
1030 1030 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1031 1031 [255]
1032 1032
1033 1033 $ killdaemons.py
1034 1034
1035 1035 Verify the global server.bundle1 option works
1036 1036
1037 1037 $ cd ..
1038 1038 $ cat > bundle2onlyserver/.hg/hgrc << EOF
1039 1039 > [server]
1040 1040 > bundle1 = false
1041 1041 > EOF
1042 1042 $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
1043 1043 $ cat hg.pid >> $DAEMON_PIDS
1044 1044 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT not-bundle2
1045 1045 requesting all changes
1046 1046 abort: remote error:
1047 1047 incompatible Mercurial client; bundle2 required
1048 1048 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1049 1049 [255]
1050 1050 $ killdaemons.py
1051 1051
1052 1052 $ hg --config devel.legacy.exchange=bundle1 clone ssh://user@dummy/bundle2onlyserver not-bundle2-ssh
1053 1053 requesting all changes
1054 1054 adding changesets
1055 1055 remote: abort: incompatible Mercurial client; bundle2 required
1056 1056 remote: (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1057 1057 transaction abort!
1058 1058 rollback completed
1059 1059 abort: stream ended unexpectedly (got 0 bytes, expected 4)
1060 1060 [255]
1061 1061
1062 1062 $ cat > bundle2onlyserver/.hg/hgrc << EOF
1063 1063 > [server]
1064 1064 > bundle1gd = false
1065 1065 > EOF
1066 1066 $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
1067 1067 $ cat hg.pid >> $DAEMON_PIDS
1068 1068
1069 1069 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
1070 1070 requesting all changes
1071 1071 abort: remote error:
1072 1072 incompatible Mercurial client; bundle2 required
1073 1073 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1074 1074 [255]
1075 1075
1076 1076 $ killdaemons.py
1077 1077
1078 1078 $ cd notgdserver
1079 1079 $ cat > .hg/hgrc << EOF
1080 1080 > [server]
1081 1081 > bundle1gd = false
1082 1082 > EOF
1083 1083 $ hg serve -p $HGPORT -d --pid-file=hg.pid
1084 1084 $ cat hg.pid >> $DAEMON_PIDS
1085 1085
1086 1086 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2-2
1087 1087 requesting all changes
1088 1088 adding changesets
1089 1089 adding manifests
1090 1090 adding file changes
1091 1091 added 1 changesets with 1 changes to 1 files
1092 1092 new changesets 96ee1d7354c4
1093 1093 updating to branch default
1094 1094 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1095 1095
1096 1096 $ killdaemons.py
1097 1097 $ cd ../bundle2onlyserver
1098 1098
1099 1099 Verify bundle1 pushes can be disabled
1100 1100
1101 1101 $ cat > .hg/hgrc << EOF
1102 1102 > [server]
1103 1103 > bundle1.push = false
1104 1104 > [web]
1105 1105 > allow_push = *
1106 1106 > push_ssl = false
1107 1107 > EOF
1108 1108
1109 1109 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E error.log
1110 1110 $ cat hg.pid >> $DAEMON_PIDS
1111 1111 $ cd ..
1112 1112
1113 1113 $ hg clone http://localhost:$HGPORT bundle2-only
1114 1114 requesting all changes
1115 1115 adding changesets
1116 1116 adding manifests
1117 1117 adding file changes
1118 1118 added 1 changesets with 1 changes to 1 files
1119 1119 new changesets 96ee1d7354c4
1120 1120 updating to branch default
1121 1121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1122 1122 $ cd bundle2-only
1123 1123 $ echo commit > foo
1124 1124 $ hg commit -m commit
1125 1125 $ hg --config devel.legacy.exchange=bundle1 push
1126 1126 pushing to http://localhost:$HGPORT/
1127 1127 searching for changes
1128 1128 abort: remote error:
1129 1129 incompatible Mercurial client; bundle2 required
1130 1130 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1131 1131 [255]
1132 1132
1133 1133 (also check with ssh)
1134 1134
1135 1135 $ hg --config devel.legacy.exchange=bundle1 push ssh://user@dummy/bundle2onlyserver
1136 1136 pushing to ssh://user@dummy/bundle2onlyserver
1137 1137 searching for changes
1138 1138 remote: abort: incompatible Mercurial client; bundle2 required
1139 1139 remote: (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1140 1140 [1]
1141 1141
1142 1142 $ hg push
1143 1143 pushing to http://localhost:$HGPORT/
1144 1144 searching for changes
1145 1145 remote: adding changesets
1146 1146 remote: adding manifests
1147 1147 remote: adding file changes
1148 1148 remote: added 1 changesets with 1 changes to 1 files
@@ -1,937 +1,937
1 1 commit hooks can see env vars
2 2 (and post-transaction one are run unlocked)
3 3
4 4
5 5 $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
6 6 > def showargs(ui, repo, hooktype, **kwargs):
7 7 > ui.write('%s Python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
8 8 > EOF
9 9
10 10 $ hg init a
11 11 $ cd a
12 12 $ cat > .hg/hgrc <<EOF
13 13 > [hooks]
14 14 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
15 15 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
16 16 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
17 17 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
18 18 > pretxncommit.tip = hg -q tip
19 19 > pre-identify = sh -c "printenv.py pre-identify 1"
20 20 > pre-cat = sh -c "printenv.py pre-cat"
21 21 > post-cat = sh -c "printenv.py post-cat"
22 22 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
23 23 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
24 24 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
25 25 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
26 26 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
27 27 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
28 28 > EOF
29 29 $ echo a > a
30 30 $ hg add a
31 31 $ hg commit -m a
32 32 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=0000000000000000000000000000000000000000
33 33 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
34 34 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
35 35 0:cb9a9f314b8b
36 36 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
37 37 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
38 38 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
39 39 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
40 40
41 41 $ hg clone . ../b
42 42 updating to branch default
43 43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 44 $ cd ../b
45 45
46 46 changegroup hooks can see env vars
47 47
48 48 $ cat > .hg/hgrc <<EOF
49 49 > [hooks]
50 50 > prechangegroup = sh -c "printenv.py prechangegroup"
51 51 > changegroup = sh -c "printenv.py changegroup"
52 52 > incoming = sh -c "printenv.py incoming"
53 53 > EOF
54 54
55 55 pretxncommit and commit hooks can see both parents of merge
56 56
57 57 $ cd ../a
58 58 $ echo b >> a
59 59 $ hg commit -m a1 -d "1 0"
60 60 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
61 61 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
62 62 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
63 63 1:ab228980c14d
64 64 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
65 65 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
66 66 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 67 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
68 68 $ hg update -C 0
69 69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 70 $ echo b > b
71 71 $ hg add b
72 72 $ hg commit -m b -d '1 0'
73 73 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 74 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
75 75 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
76 76 2:ee9deb46ab31
77 77 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
78 78 created new head
79 79 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
80 80 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
81 81 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
82 82 $ hg merge 1
83 83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 (branch merge, don't forget to commit)
85 85 $ hg commit -m merge -d '2 0'
86 86 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
87 87 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
88 88 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
89 89 3:07f3376c1e65
90 90 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
91 91 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
92 92 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
93 93 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
94 94
95 95 test generic hooks
96 96
97 97 $ hg id
98 98 pre-identify hook: HG_ARGS=id HG_HOOKNAME=pre-identify HG_HOOKTYPE=pre-identify HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None, 'template': ''} HG_PATS=[]
99 99 abort: pre-identify hook exited with status 1
100 100 [255]
101 101 $ hg cat b
102 102 pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b']
103 103 b
104 104 post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] HG_RESULT=0
105 105
106 106 $ cd ../b
107 107 $ hg pull ../a
108 108 pulling from ../a
109 109 searching for changes
110 110 prechangegroup hook: HG_HOOKNAME=prechangegroup HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 3 changesets with 2 changes to 2 files
115 115 new changesets ab228980c14d:07f3376c1e65
116 116 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
117 117 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
118 118 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
119 119 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
120 120 (run 'hg update' to get a working copy)
121 121
122 122 tag hooks can see env vars
123 123
124 124 $ cd ../a
125 125 $ cat >> .hg/hgrc <<EOF
126 126 > pretag = sh -c "printenv.py pretag"
127 127 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
128 128 > EOF
129 129 $ hg tag -d '3 0' a
130 130 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
131 131 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
132 132 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
133 133 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
134 134 4:539e4b31b6dc
135 135 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
136 136 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
137 137 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
138 138 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
139 139 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
140 140 $ hg tag -l la
141 141 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
142 142 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
143 143
144 144 pretag hook can forbid tagging
145 145
146 146 $ cat >> .hg/hgrc <<EOF
147 147 > pretag.forbid = sh -c "printenv.py pretag.forbid 1"
148 148 > EOF
149 149 $ hg tag -d '4 0' fa
150 150 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
151 151 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
152 152 abort: pretag.forbid hook exited with status 1
153 153 [255]
154 154 $ hg tag -l fla
155 155 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
156 156 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
157 157 abort: pretag.forbid hook exited with status 1
158 158 [255]
159 159
160 160 pretxncommit hook can see changeset, can roll back txn, changeset no
161 161 more there after
162 162
163 163 $ cat >> .hg/hgrc <<EOF
164 164 > pretxncommit.forbid0 = sh -c "hg tip -q"
165 165 > pretxncommit.forbid1 = sh -c "printenv.py pretxncommit.forbid 1"
166 166 > EOF
167 167 $ echo z > z
168 168 $ hg add z
169 169 $ hg -q tip
170 170 4:539e4b31b6dc
171 171 $ hg commit -m 'fail' -d '4 0'
172 172 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
173 173 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
174 174 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
175 175 5:6f611f8018c1
176 176 5:6f611f8018c1
177 177 pretxncommit.forbid hook: HG_HOOKNAME=pretxncommit.forbid1 HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
178 178 transaction abort!
179 179 txnabort Python hook: txnid,txnname
180 180 txnabort hook: HG_HOOKNAME=txnabort.1 HG_HOOKTYPE=txnabort HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
181 181 rollback completed
182 182 abort: pretxncommit.forbid1 hook exited with status 1
183 183 [255]
184 184 $ hg -q tip
185 185 4:539e4b31b6dc
186 186
187 187 (Check that no 'changelog.i.a' file were left behind)
188 188
189 189 $ ls -1 .hg/store/
190 190 00changelog.i
191 191 00manifest.i
192 192 data
193 193 fncache
194 194 journal.phaseroots
195 195 phaseroots
196 196 undo
197 197 undo.backup.fncache
198 198 undo.backupfiles
199 199 undo.phaseroots
200 200
201 201
202 202 precommit hook can prevent commit
203 203
204 204 $ cat >> .hg/hgrc <<EOF
205 205 > precommit.forbid = sh -c "printenv.py precommit.forbid 1"
206 206 > EOF
207 207 $ hg commit -m 'fail' -d '4 0'
208 208 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
209 209 precommit.forbid hook: HG_HOOKNAME=precommit.forbid HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
210 210 abort: precommit.forbid hook exited with status 1
211 211 [255]
212 212 $ hg -q tip
213 213 4:539e4b31b6dc
214 214
215 215 preupdate hook can prevent update
216 216
217 217 $ cat >> .hg/hgrc <<EOF
218 218 > preupdate = sh -c "printenv.py preupdate"
219 219 > EOF
220 220 $ hg update 1
221 221 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=ab228980c14d
222 222 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
223 223
224 224 update hook
225 225
226 226 $ cat >> .hg/hgrc <<EOF
227 227 > update = sh -c "printenv.py update"
228 228 > EOF
229 229 $ hg update
230 230 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=539e4b31b6dc
231 231 update hook: HG_ERROR=0 HG_HOOKNAME=update HG_HOOKTYPE=update HG_PARENT1=539e4b31b6dc
232 232 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 233
234 234 pushkey hook
235 235
236 236 $ cat >> .hg/hgrc <<EOF
237 237 > pushkey = sh -c "printenv.py pushkey"
238 238 > EOF
239 239 $ cd ../b
240 240 $ hg bookmark -r null foo
241 241 $ hg push -B foo ../a
242 242 pushing to ../a
243 243 searching for changes
244 244 no changes found
245 245 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
246 246 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
247 pushkey hook: HG_BUNDLE2=1 HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=foo HG_NAMESPACE=bookmark HG_NEW=0000000000000000000000000000000000000000 HG_PUSHKEYCOMPAT=1 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
247 pushkey hook: HG_BUNDLE2=1 HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_PUSHKEYCOMPAT=1 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
248 248 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
249 249 exporting bookmark foo
250 250 [1]
251 251 $ cd ../a
252 252
253 253 listkeys hook
254 254
255 255 $ cat >> .hg/hgrc <<EOF
256 256 > listkeys = sh -c "printenv.py listkeys"
257 257 > EOF
258 258 $ hg bookmark -r null bar
259 259 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
260 260 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
261 261 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
262 262 $ cd ../b
263 263 $ hg pull -B bar ../a
264 264 pulling from ../a
265 265 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
266 266 no changes found
267 267 adding remote bookmark bar
268 268 $ cd ../a
269 269
270 270 test that prepushkey can prevent incoming keys
271 271
272 272 $ cat >> .hg/hgrc <<EOF
273 273 > prepushkey = sh -c "printenv.py prepushkey.forbid 1"
274 274 > EOF
275 275 $ cd ../b
276 276 $ hg bookmark -r null baz
277 277 $ hg push -B baz ../a
278 278 pushing to ../a
279 279 searching for changes
280 280 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
281 281 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
282 282 no changes found
283 283 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
284 prepushkey.forbid hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=baz HG_NAMESPACE=bookmark HG_NEW=0000000000000000000000000000000000000000 HG_PUSHKEYCOMPAT=1 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
284 prepushkey.forbid hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_PUSHKEYCOMPAT=1 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
285 285 abort: prepushkey hook exited with status 1
286 286 [255]
287 287 $ cd ../a
288 288
289 289 test that prelistkeys can prevent listing keys
290 290
291 291 $ cat >> .hg/hgrc <<EOF
292 292 > prelistkeys = sh -c "printenv.py prelistkeys.forbid 1"
293 293 > EOF
294 294 $ hg bookmark -r null quux
295 295 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
296 296 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
297 297 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
298 298 $ cd ../b
299 299 $ hg pull -B quux ../a
300 300 pulling from ../a
301 301 prelistkeys.forbid hook: HG_HOOKNAME=prelistkeys HG_HOOKTYPE=prelistkeys HG_NAMESPACE=bookmarks
302 302 abort: prelistkeys hook exited with status 1
303 303 [255]
304 304 $ cd ../a
305 305 $ rm .hg/hgrc
306 306
307 307 prechangegroup hook can prevent incoming changes
308 308
309 309 $ cd ../b
310 310 $ hg -q tip
311 311 3:07f3376c1e65
312 312 $ cat > .hg/hgrc <<EOF
313 313 > [hooks]
314 314 > prechangegroup.forbid = sh -c "printenv.py prechangegroup.forbid 1"
315 315 > EOF
316 316 $ hg pull ../a
317 317 pulling from ../a
318 318 searching for changes
319 319 prechangegroup.forbid hook: HG_HOOKNAME=prechangegroup.forbid HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
320 320 abort: prechangegroup.forbid hook exited with status 1
321 321 [255]
322 322
323 323 pretxnchangegroup hook can see incoming changes, can roll back txn,
324 324 incoming changes no longer there after
325 325
326 326 $ cat > .hg/hgrc <<EOF
327 327 > [hooks]
328 328 > pretxnchangegroup.forbid0 = hg tip -q
329 329 > pretxnchangegroup.forbid1 = sh -c "printenv.py pretxnchangegroup.forbid 1"
330 330 > EOF
331 331 $ hg pull ../a
332 332 pulling from ../a
333 333 searching for changes
334 334 adding changesets
335 335 adding manifests
336 336 adding file changes
337 337 added 1 changesets with 1 changes to 1 files
338 338 4:539e4b31b6dc
339 339 pretxnchangegroup.forbid hook: HG_HOOKNAME=pretxnchangegroup.forbid1 HG_HOOKTYPE=pretxnchangegroup HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
340 340 transaction abort!
341 341 rollback completed
342 342 abort: pretxnchangegroup.forbid1 hook exited with status 1
343 343 [255]
344 344 $ hg -q tip
345 345 3:07f3376c1e65
346 346
347 347 outgoing hooks can see env vars
348 348
349 349 $ rm .hg/hgrc
350 350 $ cat > ../a/.hg/hgrc <<EOF
351 351 > [hooks]
352 352 > preoutgoing = sh -c "printenv.py preoutgoing"
353 353 > outgoing = sh -c "printenv.py outgoing"
354 354 > EOF
355 355 $ hg pull ../a
356 356 pulling from ../a
357 357 searching for changes
358 358 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
359 359 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
360 360 adding changesets
361 361 adding manifests
362 362 adding file changes
363 363 added 1 changesets with 1 changes to 1 files
364 364 adding remote bookmark quux
365 365 new changesets 539e4b31b6dc
366 366 (run 'hg update' to get a working copy)
367 367 $ hg rollback
368 368 repository tip rolled back to revision 3 (undo pull)
369 369
370 370 preoutgoing hook can prevent outgoing changes
371 371
372 372 $ cat >> ../a/.hg/hgrc <<EOF
373 373 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
374 374 > EOF
375 375 $ hg pull ../a
376 376 pulling from ../a
377 377 searching for changes
378 378 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
379 379 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
380 380 abort: preoutgoing.forbid hook exited with status 1
381 381 [255]
382 382
383 383 outgoing hooks work for local clones
384 384
385 385 $ cd ..
386 386 $ cat > a/.hg/hgrc <<EOF
387 387 > [hooks]
388 388 > preoutgoing = sh -c "printenv.py preoutgoing"
389 389 > outgoing = sh -c "printenv.py outgoing"
390 390 > EOF
391 391 $ hg clone a c
392 392 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
393 393 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
394 394 updating to branch default
395 395 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
396 396 $ rm -rf c
397 397
398 398 preoutgoing hook can prevent outgoing changes for local clones
399 399
400 400 $ cat >> a/.hg/hgrc <<EOF
401 401 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
402 402 > EOF
403 403 $ hg clone a zzz
404 404 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
405 405 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
406 406 abort: preoutgoing.forbid hook exited with status 1
407 407 [255]
408 408
409 409 $ cd "$TESTTMP/b"
410 410
411 411 $ cat > hooktests.py <<EOF
412 412 > from __future__ import print_function
413 413 > from mercurial import error
414 414 >
415 415 > uncallable = 0
416 416 >
417 417 > def printargs(ui, args):
418 418 > a = list(args.items())
419 419 > a.sort()
420 420 > ui.write('hook args:\n')
421 421 > for k, v in a:
422 422 > ui.write(' %s %s\n' % (k, v))
423 423 >
424 424 > def passhook(ui, repo, **args):
425 425 > printargs(ui, args)
426 426 >
427 427 > def failhook(ui, repo, **args):
428 428 > printargs(ui, args)
429 429 > return True
430 430 >
431 431 > class LocalException(Exception):
432 432 > pass
433 433 >
434 434 > def raisehook(**args):
435 435 > raise LocalException('exception from hook')
436 436 >
437 437 > def aborthook(**args):
438 438 > raise error.Abort('raise abort from hook')
439 439 >
440 440 > def brokenhook(**args):
441 441 > return 1 + {}
442 442 >
443 443 > def verbosehook(ui, **args):
444 444 > ui.note('verbose output from hook\n')
445 445 >
446 446 > def printtags(ui, repo, **args):
447 447 > ui.write('%s\n' % sorted(repo.tags()))
448 448 >
449 449 > class container:
450 450 > unreachable = 1
451 451 > EOF
452 452
453 453 $ cat > syntaxerror.py << EOF
454 454 > (foo
455 455 > EOF
456 456
457 457 test python hooks
458 458
459 459 #if windows
460 460 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
461 461 #else
462 462 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
463 463 #endif
464 464 $ export PYTHONPATH
465 465
466 466 $ echo '[hooks]' > ../a/.hg/hgrc
467 467 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
468 468 $ hg pull ../a 2>&1 | grep 'raised an exception'
469 469 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
470 470
471 471 $ echo '[hooks]' > ../a/.hg/hgrc
472 472 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
473 473 $ hg pull ../a 2>&1 | grep 'raised an exception'
474 474 error: preoutgoing.raise hook raised an exception: exception from hook
475 475
476 476 $ echo '[hooks]' > ../a/.hg/hgrc
477 477 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
478 478 $ hg pull ../a
479 479 pulling from ../a
480 480 searching for changes
481 481 error: preoutgoing.abort hook failed: raise abort from hook
482 482 abort: raise abort from hook
483 483 [255]
484 484
485 485 $ echo '[hooks]' > ../a/.hg/hgrc
486 486 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
487 487 $ hg pull ../a
488 488 pulling from ../a
489 489 searching for changes
490 490 hook args:
491 491 hooktype preoutgoing
492 492 source pull
493 493 abort: preoutgoing.fail hook failed
494 494 [255]
495 495
496 496 $ echo '[hooks]' > ../a/.hg/hgrc
497 497 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
498 498 $ hg pull ../a
499 499 pulling from ../a
500 500 searching for changes
501 501 abort: preoutgoing.uncallable hook is invalid: "hooktests.uncallable" is not callable
502 502 [255]
503 503
504 504 $ echo '[hooks]' > ../a/.hg/hgrc
505 505 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
506 506 $ hg pull ../a
507 507 pulling from ../a
508 508 searching for changes
509 509 abort: preoutgoing.nohook hook is invalid: "hooktests.nohook" is not defined
510 510 [255]
511 511
512 512 $ echo '[hooks]' > ../a/.hg/hgrc
513 513 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
514 514 $ hg pull ../a
515 515 pulling from ../a
516 516 searching for changes
517 517 abort: preoutgoing.nomodule hook is invalid: "nomodule" not in a module
518 518 [255]
519 519
520 520 $ echo '[hooks]' > ../a/.hg/hgrc
521 521 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
522 522 $ hg pull ../a
523 523 pulling from ../a
524 524 searching for changes
525 525 abort: preoutgoing.badmodule hook is invalid: import of "nomodule" failed
526 526 (run with --traceback for stack trace)
527 527 [255]
528 528
529 529 $ echo '[hooks]' > ../a/.hg/hgrc
530 530 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
531 531 $ hg pull ../a
532 532 pulling from ../a
533 533 searching for changes
534 534 abort: preoutgoing.unreachable hook is invalid: import of "hooktests.container" failed
535 535 (run with --traceback for stack trace)
536 536 [255]
537 537
538 538 $ echo '[hooks]' > ../a/.hg/hgrc
539 539 $ echo 'preoutgoing.syntaxerror = python:syntaxerror.syntaxerror' >> ../a/.hg/hgrc
540 540 $ hg pull ../a
541 541 pulling from ../a
542 542 searching for changes
543 543 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
544 544 (run with --traceback for stack trace)
545 545 [255]
546 546
547 547 The second egrep is to filter out lines like ' ^', which are slightly
548 548 different between Python 2.6 and Python 2.7.
549 549 $ hg pull ../a --traceback 2>&1 | egrep -v '^( +File| [_a-zA-Z*(])' | egrep -v '^( )+(\^)?$'
550 550 pulling from ../a
551 551 searching for changes
552 552 exception from first failed import attempt:
553 553 Traceback (most recent call last):
554 554 SyntaxError: * (glob)
555 555 exception from second failed import attempt:
556 556 Traceback (most recent call last):
557 557 ImportError: No module named hgext_syntaxerror
558 558 Traceback (most recent call last):
559 559 HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
560 560 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
561 561
562 562 $ echo '[hooks]' > ../a/.hg/hgrc
563 563 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
564 564 $ hg pull ../a
565 565 pulling from ../a
566 566 searching for changes
567 567 hook args:
568 568 hooktype preoutgoing
569 569 source pull
570 570 adding changesets
571 571 adding manifests
572 572 adding file changes
573 573 added 1 changesets with 1 changes to 1 files
574 574 adding remote bookmark quux
575 575 new changesets 539e4b31b6dc
576 576 (run 'hg update' to get a working copy)
577 577
578 578 post- python hooks that fail to *run* don't cause an abort
579 579 $ rm ../a/.hg/hgrc
580 580 $ echo '[hooks]' > .hg/hgrc
581 581 $ echo 'post-pull.broken = python:hooktests.brokenhook' >> .hg/hgrc
582 582 $ hg pull ../a
583 583 pulling from ../a
584 584 searching for changes
585 585 no changes found
586 586 error: post-pull.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
587 587 (run with --traceback for stack trace)
588 588
589 589 but post- python hooks that fail to *load* do
590 590 $ echo '[hooks]' > .hg/hgrc
591 591 $ echo 'post-pull.nomodule = python:nomodule' >> .hg/hgrc
592 592 $ hg pull ../a
593 593 pulling from ../a
594 594 searching for changes
595 595 no changes found
596 596 abort: post-pull.nomodule hook is invalid: "nomodule" not in a module
597 597 [255]
598 598
599 599 $ echo '[hooks]' > .hg/hgrc
600 600 $ echo 'post-pull.badmodule = python:nomodule.nowhere' >> .hg/hgrc
601 601 $ hg pull ../a
602 602 pulling from ../a
603 603 searching for changes
604 604 no changes found
605 605 abort: post-pull.badmodule hook is invalid: import of "nomodule" failed
606 606 (run with --traceback for stack trace)
607 607 [255]
608 608
609 609 $ echo '[hooks]' > .hg/hgrc
610 610 $ echo 'post-pull.nohook = python:hooktests.nohook' >> .hg/hgrc
611 611 $ hg pull ../a
612 612 pulling from ../a
613 613 searching for changes
614 614 no changes found
615 615 abort: post-pull.nohook hook is invalid: "hooktests.nohook" is not defined
616 616 [255]
617 617
618 618 make sure --traceback works
619 619
620 620 $ echo '[hooks]' > .hg/hgrc
621 621 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
622 622
623 623 $ echo aa > a
624 624 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
625 625 Traceback (most recent call last):
626 626
627 627 $ cd ..
628 628 $ hg init c
629 629 $ cd c
630 630
631 631 $ cat > hookext.py <<EOF
632 632 > def autohook(ui, **args):
633 633 > ui.write('Automatically installed hook\n')
634 634 >
635 635 > def reposetup(ui, repo):
636 636 > repo.ui.setconfig("hooks", "commit.auto", autohook)
637 637 > EOF
638 638 $ echo '[extensions]' >> .hg/hgrc
639 639 $ echo 'hookext = hookext.py' >> .hg/hgrc
640 640
641 641 $ touch foo
642 642 $ hg add foo
643 643 $ hg ci -d '0 0' -m 'add foo'
644 644 Automatically installed hook
645 645 $ echo >> foo
646 646 $ hg ci --debug -d '0 0' -m 'change foo'
647 647 committing files:
648 648 foo
649 649 committing manifest
650 650 committing changelog
651 651 updating the branch cache
652 652 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
653 653 calling hook commit.auto: hgext_hookext.autohook
654 654 Automatically installed hook
655 655
656 656 $ hg showconfig hooks
657 657 hooks.commit.auto=<function autohook at *> (glob)
658 658
659 659 test python hook configured with python:[file]:[hook] syntax
660 660
661 661 $ cd ..
662 662 $ mkdir d
663 663 $ cd d
664 664 $ hg init repo
665 665 $ mkdir hooks
666 666
667 667 $ cd hooks
668 668 $ cat > testhooks.py <<EOF
669 669 > def testhook(ui, **args):
670 670 > ui.write('hook works\n')
671 671 > EOF
672 672 $ echo '[hooks]' > ../repo/.hg/hgrc
673 673 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
674 674
675 675 $ cd ../repo
676 676 $ hg commit -d '0 0'
677 677 hook works
678 678 nothing changed
679 679 [1]
680 680
681 681 $ echo '[hooks]' > .hg/hgrc
682 682 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
683 683 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
684 684
685 685 $ hg up null
686 686 loading update.ne hook failed:
687 687 abort: $ENOENT$: $TESTTMP/d/repo/nonexistent.py
688 688 [255]
689 689
690 690 $ hg id
691 691 loading pre-identify.npmd hook failed:
692 692 abort: No module named repo!
693 693 [255]
694 694
695 695 $ cd ../../b
696 696
697 697 make sure --traceback works on hook import failure
698 698
699 699 $ cat > importfail.py <<EOF
700 700 > import somebogusmodule
701 701 > # dereference something in the module to force demandimport to load it
702 702 > somebogusmodule.whatever
703 703 > EOF
704 704
705 705 $ echo '[hooks]' > .hg/hgrc
706 706 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
707 707
708 708 $ echo a >> a
709 709 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
710 710 exception from first failed import attempt:
711 711 Traceback (most recent call last):
712 712 ImportError: No module named somebogusmodule
713 713 exception from second failed import attempt:
714 714 Traceback (most recent call last):
715 715 ImportError: No module named hgext_importfail
716 716 Traceback (most recent call last):
717 717 HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
718 718 abort: precommit.importfail hook is invalid: import of "importfail" failed
719 719
720 720 Issue1827: Hooks Update & Commit not completely post operation
721 721
722 722 commit and update hooks should run after command completion. The largefiles
723 723 use demonstrates a recursive wlock, showing the hook doesn't run until the
724 724 final release (and dirstate flush).
725 725
726 726 $ echo '[hooks]' > .hg/hgrc
727 727 $ echo 'commit = hg id' >> .hg/hgrc
728 728 $ echo 'update = hg id' >> .hg/hgrc
729 729 $ echo bb > a
730 730 $ hg ci -ma
731 731 223eafe2750c tip
732 732 $ hg up 0 --config extensions.largefiles=
733 733 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
734 734 cb9a9f314b8b
735 735 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
736 736
737 737 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
738 738 that is passed to pre/post hooks
739 739
740 740 $ echo '[hooks]' > .hg/hgrc
741 741 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
742 742 $ hg id
743 743 cb9a9f314b8b
744 744 $ hg id --verbose
745 745 calling hook pre-identify: hooktests.verbosehook
746 746 verbose output from hook
747 747 cb9a9f314b8b
748 748
749 749 Ensure hooks can be prioritized
750 750
751 751 $ echo '[hooks]' > .hg/hgrc
752 752 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
753 753 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
754 754 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
755 755 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
756 756 $ hg id --verbose
757 757 calling hook pre-identify.b: hooktests.verbosehook
758 758 verbose output from hook
759 759 calling hook pre-identify.a: hooktests.verbosehook
760 760 verbose output from hook
761 761 calling hook pre-identify.c: hooktests.verbosehook
762 762 verbose output from hook
763 763 cb9a9f314b8b
764 764
765 765 new tags must be visible in pretxncommit (issue3210)
766 766
767 767 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
768 768 $ hg tag -f foo
769 769 ['a', 'foo', 'tip']
770 770
771 771 post-init hooks must not crash (issue4983)
772 772 This also creates the `to` repo for the next test block.
773 773
774 774 $ cd ..
775 775 $ cat << EOF >> hgrc-with-post-init-hook
776 776 > [hooks]
777 777 > post-init = sh -c "printenv.py post-init"
778 778 > EOF
779 779 $ HGRCPATH=hgrc-with-post-init-hook hg init to
780 780 post-init hook: HG_ARGS=init to HG_HOOKNAME=post-init HG_HOOKTYPE=post-init HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''} HG_PATS=['to'] HG_RESULT=0
781 781
782 782 new commits must be visible in pretxnchangegroup (issue3428)
783 783
784 784 $ echo '[hooks]' >> to/.hg/hgrc
785 785 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
786 786 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
787 787 $ echo a >> to/a
788 788 $ hg --cwd to ci -Ama
789 789 adding a
790 790 $ hg clone to from
791 791 updating to branch default
792 792 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
793 793 $ echo aa >> from/a
794 794 $ hg --cwd from ci -mb
795 795 $ hg --cwd from push
796 796 pushing to $TESTTMP/to
797 797 searching for changes
798 798 changeset: 0:cb9a9f314b8b
799 799 tag: tip
800 800 user: test
801 801 date: Thu Jan 01 00:00:00 1970 +0000
802 802 summary: a
803 803
804 804 adding changesets
805 805 adding manifests
806 806 adding file changes
807 807 added 1 changesets with 1 changes to 1 files
808 808 changeset: 1:9836a07b9b9d
809 809 tag: tip
810 810 user: test
811 811 date: Thu Jan 01 00:00:00 1970 +0000
812 812 summary: b
813 813
814 814
815 815 pretxnclose hook failure should abort the transaction
816 816
817 817 $ hg init txnfailure
818 818 $ cd txnfailure
819 819 $ touch a && hg commit -Aqm a
820 820 $ cat >> .hg/hgrc <<EOF
821 821 > [hooks]
822 822 > pretxnclose.error = exit 1
823 823 > EOF
824 824 $ hg strip -r 0 --config extensions.strip=
825 825 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
826 826 saved backup bundle to * (glob)
827 827 transaction abort!
828 828 rollback completed
829 829 strip failed, backup bundle stored in * (glob)
830 830 abort: pretxnclose.error hook exited with status 1
831 831 [255]
832 832 $ hg recover
833 833 no interrupted transaction available
834 834 [1]
835 835 $ cd ..
836 836
837 837 check whether HG_PENDING makes pending changes only in related
838 838 repositories visible to an external hook.
839 839
840 840 (emulate a transaction running concurrently by copied
841 841 .hg/store/00changelog.i.a in subsequent test)
842 842
843 843 $ cat > $TESTTMP/savepending.sh <<EOF
844 844 > cp .hg/store/00changelog.i.a .hg/store/00changelog.i.a.saved
845 845 > exit 1 # to avoid adding new revision for subsequent tests
846 846 > EOF
847 847 $ cd a
848 848 $ hg tip -q
849 849 4:539e4b31b6dc
850 850 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" commit -m "invisible"
851 851 transaction abort!
852 852 rollback completed
853 853 abort: pretxnclose hook exited with status 1
854 854 [255]
855 855 $ cp .hg/store/00changelog.i.a.saved .hg/store/00changelog.i.a
856 856
857 857 (check (in)visibility of new changeset while transaction running in
858 858 repo)
859 859
860 860 $ cat > $TESTTMP/checkpending.sh <<EOF
861 861 > echo '@a'
862 862 > hg -R "$TESTTMP/a" tip -q
863 863 > echo '@a/nested'
864 864 > hg -R "$TESTTMP/a/nested" tip -q
865 865 > exit 1 # to avoid adding new revision for subsequent tests
866 866 > EOF
867 867 $ hg init nested
868 868 $ cd nested
869 869 $ echo a > a
870 870 $ hg add a
871 871 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" commit -m '#0'
872 872 @a
873 873 4:539e4b31b6dc
874 874 @a/nested
875 875 0:bf5e395ced2c
876 876 transaction abort!
877 877 rollback completed
878 878 abort: pretxnclose hook exited with status 1
879 879 [255]
880 880
881 881 Hook from untrusted hgrc are reported as failure
882 882 ================================================
883 883
884 884 $ cat << EOF > $TESTTMP/untrusted.py
885 885 > from mercurial import scmutil, util
886 886 > def uisetup(ui):
887 887 > class untrustedui(ui.__class__):
888 888 > def _trusted(self, fp, f):
889 889 > if util.normpath(fp.name).endswith('untrusted/.hg/hgrc'):
890 890 > return False
891 891 > return super(untrustedui, self)._trusted(fp, f)
892 892 > ui.__class__ = untrustedui
893 893 > EOF
894 894 $ cat << EOF >> $HGRCPATH
895 895 > [extensions]
896 896 > untrusted=$TESTTMP/untrusted.py
897 897 > EOF
898 898 $ hg init untrusted
899 899 $ cd untrusted
900 900
901 901 Non-blocking hook
902 902 -----------------
903 903
904 904 $ cat << EOF >> .hg/hgrc
905 905 > [hooks]
906 906 > txnclose.testing=echo txnclose hook called
907 907 > EOF
908 908 $ touch a && hg commit -Aqm a
909 909 warning: untrusted hook txnclose.testing not executed
910 910 $ hg log
911 911 changeset: 0:3903775176ed
912 912 tag: tip
913 913 user: test
914 914 date: Thu Jan 01 00:00:00 1970 +0000
915 915 summary: a
916 916
917 917
918 918 Non-blocking hook
919 919 -----------------
920 920
921 921 $ cat << EOF >> .hg/hgrc
922 922 > [hooks]
923 923 > pretxnclose.testing=echo pre-txnclose hook called
924 924 > EOF
925 925 $ touch b && hg commit -Aqm a
926 926 transaction abort!
927 927 rollback completed
928 928 abort: untrusted hook pretxnclose.testing not executed
929 929 (see 'hg help config.trusted')
930 930 [255]
931 931 $ hg log
932 932 changeset: 0:3903775176ed
933 933 tag: tip
934 934 user: test
935 935 date: Thu Jan 01 00:00:00 1970 +0000
936 936 summary: a
937 937
General Comments 0
You need to be logged in to leave comments. Login now