##// END OF EJS Templates
transaction: run abort callback in all cases...
marmoute -
r50889:3128018e default
parent child Browse files
Show More
@@ -1,785 +1,786 b''
1 1 # transaction.py - simple journaling scheme for mercurial
2 2 #
3 3 # This transaction scheme is intended to gracefully handle program
4 4 # errors and interruptions. More serious failures like system crashes
5 5 # can be recovered with an fsck-like tool. As the whole repository is
6 6 # effectively log-structured, this should amount to simply truncating
7 7 # anything that isn't referenced in the changelog.
8 8 #
9 9 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
10 10 #
11 11 # This software may be used and distributed according to the terms of the
12 12 # GNU General Public License version 2 or any later version.
13 13
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 error,
18 18 pycompat,
19 19 util,
20 20 )
21 21 from .utils import stringutil
22 22
23 23 version = 2
24 24
25 25 GEN_GROUP_ALL = b'all'
26 26 GEN_GROUP_PRE_FINALIZE = b'prefinalize'
27 27 GEN_GROUP_POST_FINALIZE = b'postfinalize'
28 28
29 29
30 30 def active(func):
31 31 def _active(self, *args, **kwds):
32 32 if self._count == 0:
33 33 raise error.ProgrammingError(
34 34 b'cannot use transaction when it is already committed/aborted'
35 35 )
36 36 return func(self, *args, **kwds)
37 37
38 38 return _active
39 39
40 40
41 41 def _playback(
42 42 journal,
43 43 report,
44 44 opener,
45 45 vfsmap,
46 46 entries,
47 47 backupentries,
48 48 unlink=True,
49 49 checkambigfiles=None,
50 50 ):
51 51 for f, o in sorted(dict(entries).items()):
52 52 if o or not unlink:
53 53 checkambig = checkambigfiles and (f, b'') in checkambigfiles
54 54 try:
55 55 fp = opener(f, b'a', checkambig=checkambig)
56 56 if fp.tell() < o:
57 57 raise error.Abort(
58 58 _(
59 59 b"attempted to truncate %s to %d bytes, but it was "
60 60 b"already %d bytes\n"
61 61 )
62 62 % (f, o, fp.tell())
63 63 )
64 64 fp.truncate(o)
65 65 fp.close()
66 66 except IOError:
67 67 report(_(b"failed to truncate %s\n") % f)
68 68 raise
69 69 else:
70 70 try:
71 71 opener.unlink(f)
72 72 except FileNotFoundError:
73 73 pass
74 74
75 75 backupfiles = []
76 76 for l, f, b, c in backupentries:
77 77 if l not in vfsmap and c:
78 78 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
79 79 vfs = vfsmap[l]
80 80 try:
81 81 if f and b:
82 82 filepath = vfs.join(f)
83 83 backuppath = vfs.join(b)
84 84 checkambig = checkambigfiles and (f, l) in checkambigfiles
85 85 try:
86 86 util.copyfile(backuppath, filepath, checkambig=checkambig)
87 87 backupfiles.append(b)
88 88 except IOError as exc:
89 89 e_msg = stringutil.forcebytestr(exc)
90 90 report(_(b"failed to recover %s (%s)\n") % (f, e_msg))
91 91 else:
92 92 target = f or b
93 93 try:
94 94 vfs.unlink(target)
95 95 except FileNotFoundError:
96 96 pass
97 97 except (IOError, OSError, error.Abort):
98 98 if not c:
99 99 raise
100 100
101 101 backuppath = b"%s.backupfiles" % journal
102 102 if opener.exists(backuppath):
103 103 opener.unlink(backuppath)
104 104 opener.unlink(journal)
105 105 try:
106 106 for f in backupfiles:
107 107 if opener.exists(f):
108 108 opener.unlink(f)
109 109 except (IOError, OSError, error.Abort):
110 110 # only pure backup file remains, it is sage to ignore any error
111 111 pass
112 112
113 113
114 114 class transaction(util.transactional):
115 115 def __init__(
116 116 self,
117 117 report,
118 118 opener,
119 119 vfsmap,
120 120 journalname,
121 121 undoname=None,
122 122 after=None,
123 123 createmode=None,
124 124 validator=None,
125 125 releasefn=None,
126 126 checkambigfiles=None,
127 127 name='<unnamed>',
128 128 ):
129 129 """Begin a new transaction
130 130
131 131 Begins a new transaction that allows rolling back writes in the event of
132 132 an exception.
133 133
134 134 * `after`: called after the transaction has been committed
135 135 * `createmode`: the mode of the journal file that will be created
136 136 * `releasefn`: called after releasing (with transaction and result)
137 137
138 138 `checkambigfiles` is a set of (path, vfs-location) tuples,
139 139 which determine whether file stat ambiguity should be avoided
140 140 for corresponded files.
141 141 """
142 142 self._count = 1
143 143 self._usages = 1
144 144 self._report = report
145 145 # a vfs to the store content
146 146 self._opener = opener
147 147 # a map to access file in various {location -> vfs}
148 148 vfsmap = vfsmap.copy()
149 149 vfsmap[b''] = opener # set default value
150 150 self._vfsmap = vfsmap
151 151 self._after = after
152 152 self._offsetmap = {}
153 153 self._newfiles = set()
154 154 self._journal = journalname
155 155 self._undoname = undoname
156 156 self._queue = []
157 157 # A callback to do something just after releasing transaction.
158 158 if releasefn is None:
159 159 releasefn = lambda tr, success: None
160 160 self._releasefn = releasefn
161 161
162 162 self._checkambigfiles = set()
163 163 if checkambigfiles:
164 164 self._checkambigfiles.update(checkambigfiles)
165 165
166 166 self._names = [name]
167 167
168 168 # A dict dedicated to precisely tracking the changes introduced in the
169 169 # transaction.
170 170 self.changes = {}
171 171
172 172 # a dict of arguments to be passed to hooks
173 173 self.hookargs = {}
174 174 self._file = opener.open(self._journal, b"w+")
175 175
176 176 # a list of ('location', 'path', 'backuppath', cache) entries.
177 177 # - if 'backuppath' is empty, no file existed at backup time
178 178 # - if 'path' is empty, this is a temporary transaction file
179 179 # - if 'location' is not empty, the path is outside main opener reach.
180 180 # use 'location' value as a key in a vfsmap to find the right 'vfs'
181 181 # (cache is currently unused)
182 182 self._backupentries = []
183 183 self._backupmap = {}
184 184 self._backupjournal = b"%s.backupfiles" % self._journal
185 185 self._backupsfile = opener.open(self._backupjournal, b'w')
186 186 self._backupsfile.write(b'%d\n' % version)
187 187
188 188 if createmode is not None:
189 189 opener.chmod(self._journal, createmode & 0o666)
190 190 opener.chmod(self._backupjournal, createmode & 0o666)
191 191
192 192 # hold file generations to be performed on commit
193 193 self._filegenerators = {}
194 194 # hold callback to write pending data for hooks
195 195 self._pendingcallback = {}
196 196 # True is any pending data have been written ever
197 197 self._anypending = False
198 198 # holds callback to call when writing the transaction
199 199 self._finalizecallback = {}
200 200 # holds callback to call when validating the transaction
201 201 # should raise exception if anything is wrong
202 202 self._validatecallback = {}
203 203 if validator is not None:
204 204 self._validatecallback[b'001-userhooks'] = validator
205 205 # hold callback for post transaction close
206 206 self._postclosecallback = {}
207 207 # holds callbacks to call during abort
208 208 self._abortcallback = {}
209 209
210 210 def __repr__(self):
211 211 name = '/'.join(self._names)
212 212 return '<transaction name=%s, count=%d, usages=%d>' % (
213 213 name,
214 214 self._count,
215 215 self._usages,
216 216 )
217 217
218 218 def __del__(self):
219 219 if self._journal:
220 220 self._abort()
221 221
222 222 @property
223 223 def finalized(self):
224 224 return self._finalizecallback is None
225 225
226 226 @active
227 227 def startgroup(self):
228 228 """delay registration of file entry
229 229
230 230 This is used by strip to delay vision of strip offset. The transaction
231 231 sees either none or all of the strip actions to be done."""
232 232 self._queue.append([])
233 233
234 234 @active
235 235 def endgroup(self):
236 236 """apply delayed registration of file entry.
237 237
238 238 This is used by strip to delay vision of strip offset. The transaction
239 239 sees either none or all of the strip actions to be done."""
240 240 q = self._queue.pop()
241 241 for f, o in q:
242 242 self._addentry(f, o)
243 243
244 244 @active
245 245 def add(self, file, offset):
246 246 """record the state of an append-only file before update"""
247 247 if (
248 248 file in self._newfiles
249 249 or file in self._offsetmap
250 250 or file in self._backupmap
251 251 ):
252 252 return
253 253 if self._queue:
254 254 self._queue[-1].append((file, offset))
255 255 return
256 256
257 257 self._addentry(file, offset)
258 258
259 259 def _addentry(self, file, offset):
260 260 """add a append-only entry to memory and on-disk state"""
261 261 if (
262 262 file in self._newfiles
263 263 or file in self._offsetmap
264 264 or file in self._backupmap
265 265 ):
266 266 return
267 267 if offset:
268 268 self._offsetmap[file] = offset
269 269 else:
270 270 self._newfiles.add(file)
271 271 # add enough data to the journal to do the truncate
272 272 self._file.write(b"%s\0%d\n" % (file, offset))
273 273 self._file.flush()
274 274
275 275 @active
276 276 def addbackup(self, file, hardlink=True, location=b''):
277 277 """Adds a backup of the file to the transaction
278 278
279 279 Calling addbackup() creates a hardlink backup of the specified file
280 280 that is used to recover the file in the event of the transaction
281 281 aborting.
282 282
283 283 * `file`: the file path, relative to .hg/store
284 284 * `hardlink`: use a hardlink to quickly create the backup
285 285 """
286 286 if self._queue:
287 287 msg = b'cannot use transaction.addbackup inside "group"'
288 288 raise error.ProgrammingError(msg)
289 289
290 290 if (
291 291 file in self._newfiles
292 292 or file in self._offsetmap
293 293 or file in self._backupmap
294 294 ):
295 295 return
296 296 vfs = self._vfsmap[location]
297 297 dirname, filename = vfs.split(file)
298 298 backupfilename = b"%s.backup.%s" % (self._journal, filename)
299 299 backupfile = vfs.reljoin(dirname, backupfilename)
300 300 if vfs.exists(file):
301 301 filepath = vfs.join(file)
302 302 backuppath = vfs.join(backupfile)
303 303 util.copyfile(filepath, backuppath, hardlink=hardlink)
304 304 else:
305 305 backupfile = b''
306 306
307 307 self._addbackupentry((location, file, backupfile, False))
308 308
309 309 def _addbackupentry(self, entry):
310 310 """register a new backup entry and write it to disk"""
311 311 self._backupentries.append(entry)
312 312 self._backupmap[entry[1]] = len(self._backupentries) - 1
313 313 self._backupsfile.write(b"%s\0%s\0%s\0%d\n" % entry)
314 314 self._backupsfile.flush()
315 315
316 316 @active
317 317 def registertmp(self, tmpfile, location=b''):
318 318 """register a temporary transaction file
319 319
320 320 Such files will be deleted when the transaction exits (on both
321 321 failure and success).
322 322 """
323 323 self._addbackupentry((location, b'', tmpfile, False))
324 324
325 325 @active
326 326 def addfilegenerator(
327 327 self,
328 328 genid,
329 329 filenames,
330 330 genfunc,
331 331 order=0,
332 332 location=b'',
333 333 post_finalize=False,
334 334 ):
335 335 """add a function to generates some files at transaction commit
336 336
337 337 The `genfunc` argument is a function capable of generating proper
338 338 content of each entry in the `filename` tuple.
339 339
340 340 At transaction close time, `genfunc` will be called with one file
341 341 object argument per entries in `filenames`.
342 342
343 343 The transaction itself is responsible for the backup, creation and
344 344 final write of such file.
345 345
346 346 The `genid` argument is used to ensure the same set of file is only
347 347 generated once. Call to `addfilegenerator` for a `genid` already
348 348 present will overwrite the old entry.
349 349
350 350 The `order` argument may be used to control the order in which multiple
351 351 generator will be executed.
352 352
353 353 The `location` arguments may be used to indicate the files are located
354 354 outside of the the standard directory for transaction. It should match
355 355 one of the key of the `transaction.vfsmap` dictionary.
356 356
357 357 The `post_finalize` argument can be set to `True` for file generation
358 358 that must be run after the transaction has been finalized.
359 359 """
360 360 # For now, we are unable to do proper backup and restore of custom vfs
361 361 # but for bookmarks that are handled outside this mechanism.
362 362 entry = (order, filenames, genfunc, location, post_finalize)
363 363 self._filegenerators[genid] = entry
364 364
365 365 @active
366 366 def removefilegenerator(self, genid):
367 367 """reverse of addfilegenerator, remove a file generator function"""
368 368 if genid in self._filegenerators:
369 369 del self._filegenerators[genid]
370 370
371 371 def _generatefiles(self, suffix=b'', group=GEN_GROUP_ALL):
372 372 # write files registered for generation
373 373 any = False
374 374
375 375 if group == GEN_GROUP_ALL:
376 376 skip_post = skip_pre = False
377 377 else:
378 378 skip_pre = group == GEN_GROUP_POST_FINALIZE
379 379 skip_post = group == GEN_GROUP_PRE_FINALIZE
380 380
381 381 for id, entry in sorted(self._filegenerators.items()):
382 382 any = True
383 383 order, filenames, genfunc, location, post_finalize = entry
384 384
385 385 # for generation at closing, check if it's before or after finalize
386 386 if skip_post and post_finalize:
387 387 continue
388 388 elif skip_pre and not post_finalize:
389 389 continue
390 390
391 391 vfs = self._vfsmap[location]
392 392 files = []
393 393 try:
394 394 for name in filenames:
395 395 name += suffix
396 396 if suffix:
397 397 self.registertmp(name, location=location)
398 398 checkambig = False
399 399 else:
400 400 self.addbackup(name, location=location)
401 401 checkambig = (name, location) in self._checkambigfiles
402 402 files.append(
403 403 vfs(name, b'w', atomictemp=True, checkambig=checkambig)
404 404 )
405 405 genfunc(*files)
406 406 for f in files:
407 407 f.close()
408 408 # skip discard() loop since we're sure no open file remains
409 409 del files[:]
410 410 finally:
411 411 for f in files:
412 412 f.discard()
413 413 return any
414 414
415 415 @active
416 416 def findoffset(self, file):
417 417 if file in self._newfiles:
418 418 return 0
419 419 return self._offsetmap.get(file)
420 420
421 421 @active
422 422 def readjournal(self):
423 423 self._file.seek(0)
424 424 entries = []
425 425 for l in self._file.readlines():
426 426 file, troffset = l.split(b'\0')
427 427 entries.append((file, int(troffset)))
428 428 return entries
429 429
430 430 @active
431 431 def replace(self, file, offset):
432 432 """
433 433 replace can only replace already committed entries
434 434 that are not pending in the queue
435 435 """
436 436 if file in self._newfiles:
437 437 if not offset:
438 438 return
439 439 self._newfiles.remove(file)
440 440 self._offsetmap[file] = offset
441 441 elif file in self._offsetmap:
442 442 if not offset:
443 443 del self._offsetmap[file]
444 444 self._newfiles.add(file)
445 445 else:
446 446 self._offsetmap[file] = offset
447 447 else:
448 448 raise KeyError(file)
449 449 self._file.write(b"%s\0%d\n" % (file, offset))
450 450 self._file.flush()
451 451
452 452 @active
453 453 def nest(self, name='<unnamed>'):
454 454 self._count += 1
455 455 self._usages += 1
456 456 self._names.append(name)
457 457 return self
458 458
459 459 def release(self):
460 460 if self._count > 0:
461 461 self._usages -= 1
462 462 if self._names:
463 463 self._names.pop()
464 464 # if the transaction scopes are left without being closed, fail
465 465 if self._count > 0 and self._usages == 0:
466 466 self._abort()
467 467
468 468 def running(self):
469 469 return self._count > 0
470 470
471 471 def addpending(self, category, callback):
472 472 """add a callback to be called when the transaction is pending
473 473
474 474 The transaction will be given as callback's first argument.
475 475
476 476 Category is a unique identifier to allow overwriting an old callback
477 477 with a newer callback.
478 478 """
479 479 self._pendingcallback[category] = callback
480 480
481 481 @active
482 482 def writepending(self):
483 483 """write pending file to temporary version
484 484
485 485 This is used to allow hooks to view a transaction before commit"""
486 486 categories = sorted(self._pendingcallback)
487 487 for cat in categories:
488 488 # remove callback since the data will have been flushed
489 489 any = self._pendingcallback.pop(cat)(self)
490 490 self._anypending = self._anypending or any
491 491 self._anypending |= self._generatefiles(suffix=b'.pending')
492 492 return self._anypending
493 493
494 494 @active
495 495 def hasfinalize(self, category):
496 496 """check is a callback already exist for a category"""
497 497 return category in self._finalizecallback
498 498
499 499 @active
500 500 def addfinalize(self, category, callback):
501 501 """add a callback to be called when the transaction is closed
502 502
503 503 The transaction will be given as callback's first argument.
504 504
505 505 Category is a unique identifier to allow overwriting old callbacks with
506 506 newer callbacks.
507 507 """
508 508 self._finalizecallback[category] = callback
509 509
510 510 @active
511 511 def addpostclose(self, category, callback):
512 512 """add or replace a callback to be called after the transaction closed
513 513
514 514 The transaction will be given as callback's first argument.
515 515
516 516 Category is a unique identifier to allow overwriting an old callback
517 517 with a newer callback.
518 518 """
519 519 self._postclosecallback[category] = callback
520 520
521 521 @active
522 522 def getpostclose(self, category):
523 523 """return a postclose callback added before, or None"""
524 524 return self._postclosecallback.get(category, None)
525 525
526 526 @active
527 527 def addabort(self, category, callback):
528 528 """add a callback to be called when the transaction is aborted.
529 529
530 530 The transaction will be given as the first argument to the callback.
531 531
532 532 Category is a unique identifier to allow overwriting an old callback
533 533 with a newer callback.
534 534 """
535 535 self._abortcallback[category] = callback
536 536
537 537 @active
538 538 def addvalidator(self, category, callback):
539 539 """adds a callback to be called when validating the transaction.
540 540
541 541 The transaction will be given as the first argument to the callback.
542 542
543 543 callback should raise exception if to abort transaction"""
544 544 self._validatecallback[category] = callback
545 545
546 546 @active
547 547 def close(self):
548 548 '''commit the transaction'''
549 549 if self._count == 1:
550 550 for category in sorted(self._validatecallback):
551 551 self._validatecallback[category](self)
552 552 self._validatecallback = None # Help prevent cycles.
553 553 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
554 554 while self._finalizecallback:
555 555 callbacks = self._finalizecallback
556 556 self._finalizecallback = {}
557 557 categories = sorted(callbacks)
558 558 for cat in categories:
559 559 callbacks[cat](self)
560 560 # Prevent double usage and help clear cycles.
561 561 self._finalizecallback = None
562 562 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
563 563
564 564 self._count -= 1
565 565 if self._count != 0:
566 566 return
567 567 self._file.close()
568 568 self._backupsfile.close()
569 569 # cleanup temporary files
570 570 for l, f, b, c in self._backupentries:
571 571 if l not in self._vfsmap and c:
572 572 self._report(
573 573 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
574 574 )
575 575 continue
576 576 vfs = self._vfsmap[l]
577 577 if not f and b and vfs.exists(b):
578 578 try:
579 579 vfs.unlink(b)
580 580 except (IOError, OSError, error.Abort) as inst:
581 581 if not c:
582 582 raise
583 583 # Abort may be raise by read only opener
584 584 self._report(
585 585 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
586 586 )
587 587 self._offsetmap = {}
588 588 self._newfiles = set()
589 589 self._writeundo()
590 590 if self._after:
591 591 self._after()
592 592 self._after = None # Help prevent cycles.
593 593 if self._opener.isfile(self._backupjournal):
594 594 self._opener.unlink(self._backupjournal)
595 595 if self._opener.isfile(self._journal):
596 596 self._opener.unlink(self._journal)
597 597 for l, _f, b, c in self._backupentries:
598 598 if l not in self._vfsmap and c:
599 599 self._report(
600 600 b"couldn't remove %s: unknown cache location"
601 601 b"%s\n" % (b, l)
602 602 )
603 603 continue
604 604 vfs = self._vfsmap[l]
605 605 if b and vfs.exists(b):
606 606 try:
607 607 vfs.unlink(b)
608 608 except (IOError, OSError, error.Abort) as inst:
609 609 if not c:
610 610 raise
611 611 # Abort may be raise by read only opener
612 612 self._report(
613 613 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
614 614 )
615 615 self._backupentries = []
616 616 self._journal = None
617 617
618 618 self._releasefn(self, True) # notify success of closing transaction
619 619 self._releasefn = None # Help prevent cycles.
620 620
621 621 # run post close action
622 622 categories = sorted(self._postclosecallback)
623 623 for cat in categories:
624 624 self._postclosecallback[cat](self)
625 625 # Prevent double usage and help clear cycles.
626 626 self._postclosecallback = None
627 627
628 628 @active
629 629 def abort(self):
630 630 """abort the transaction (generally called on error, or when the
631 631 transaction is not explicitly committed before going out of
632 632 scope)"""
633 633 self._abort()
634 634
635 635 def _writeundo(self):
636 636 """write transaction data for possible future undo call"""
637 637 if self._undoname is None:
638 638 return
639 639
640 640 undo_backup_path = b"%s.backupfiles" % self._undoname
641 641 undobackupfile = self._opener.open(undo_backup_path, b'w')
642 642 undobackupfile.write(b'%d\n' % version)
643 643 for l, f, b, c in self._backupentries:
644 644 if not f: # temporary file
645 645 continue
646 646 if not b:
647 647 u = b''
648 648 else:
649 649 if l not in self._vfsmap and c:
650 650 self._report(
651 651 b"couldn't remove %s: unknown cache location"
652 652 b"%s\n" % (b, l)
653 653 )
654 654 continue
655 655 vfs = self._vfsmap[l]
656 656 base, name = vfs.split(b)
657 657 assert name.startswith(self._journal), name
658 658 uname = name.replace(self._journal, self._undoname, 1)
659 659 u = vfs.reljoin(base, uname)
660 660 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
661 661 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
662 662 undobackupfile.close()
663 663
664 664 def _abort(self):
665 665 entries = self.readjournal()
666 666 self._count = 0
667 667 self._usages = 0
668 668 self._file.close()
669 669 self._backupsfile.close()
670 670
671 671 quick = self._can_quick_abort(entries)
672 672 try:
673 if not quick:
674 self._report(_(b"transaction abort!\n"))
675 for cat in sorted(self._abortcallback):
676 self._abortcallback[cat](self)
677 # Prevent double usage and help clear cycles.
678 self._abortcallback = None
673 679 if quick:
674 680 self._do_quick_abort(entries)
675 681 else:
676 682 self._do_full_abort(entries)
677 683 finally:
678 684 self._journal = None
679 685 self._releasefn(self, False) # notify failure of transaction
680 686 self._releasefn = None # Help prevent cycles.
681 687
682 688 def _can_quick_abort(self, entries):
683 689 """False if any semantic content have been written on disk
684 690
685 691 True if nothing, except temporary files has been writen on disk."""
686 692 if entries:
687 693 return False
688 694 if self._backupentries:
689 695 return False
690 696 return True
691 697
692 698 def _do_quick_abort(self, entries):
693 699 """(Silently) do a quick cleanup (see _can_quick_abort)"""
694 700 assert self._can_quick_abort(entries)
695 701 if self._backupjournal:
696 702 self._opener.unlink(self._backupjournal)
697 703 if self._journal:
698 704 self._opener.unlink(self._journal)
699 705
700 706 def _do_full_abort(self, entries):
701 707 """(Noisily) rollback all the change introduced by the transaction"""
702 self._report(_(b"transaction abort!\n"))
703 708 try:
704 for cat in sorted(self._abortcallback):
705 self._abortcallback[cat](self)
706 # Prevent double usage and help clear cycles.
707 self._abortcallback = None
708 709 _playback(
709 710 self._journal,
710 711 self._report,
711 712 self._opener,
712 713 self._vfsmap,
713 714 entries,
714 715 self._backupentries,
715 716 False,
716 717 checkambigfiles=self._checkambigfiles,
717 718 )
718 719 self._report(_(b"rollback completed\n"))
719 720 except BaseException as exc:
720 721 self._report(_(b"rollback failed - please run hg recover\n"))
721 722 self._report(
722 723 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
723 724 )
724 725
725 726
726 727 BAD_VERSION_MSG = _(
727 728 b"journal was created by a different version of Mercurial\n"
728 729 )
729 730
730 731
731 732 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
732 733 """Rolls back the transaction contained in the given file
733 734
734 735 Reads the entries in the specified file, and the corresponding
735 736 '*.backupfiles' file, to recover from an incomplete transaction.
736 737
737 738 * `file`: a file containing a list of entries, specifying where
738 739 to truncate each file. The file should contain a list of
739 740 file\0offset pairs, delimited by newlines. The corresponding
740 741 '*.backupfiles' file should contain a list of file\0backupfile
741 742 pairs, delimited by \0.
742 743
743 744 `checkambigfiles` is a set of (path, vfs-location) tuples,
744 745 which determine whether file stat ambiguity should be avoided at
745 746 restoring corresponded files.
746 747 """
747 748 entries = []
748 749 backupentries = []
749 750
750 751 with opener.open(file) as fp:
751 752 lines = fp.readlines()
752 753 for l in lines:
753 754 try:
754 755 f, o = l.split(b'\0')
755 756 entries.append((f, int(o)))
756 757 except ValueError:
757 758 report(
758 759 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
759 760 )
760 761
761 762 backupjournal = b"%s.backupfiles" % file
762 763 if opener.exists(backupjournal):
763 764 fp = opener.open(backupjournal)
764 765 lines = fp.readlines()
765 766 if lines:
766 767 ver = lines[0][:-1]
767 768 if ver != (b'%d' % version):
768 769 report(BAD_VERSION_MSG)
769 770 else:
770 771 for line in lines[1:]:
771 772 if line:
772 773 # Shave off the trailing newline
773 774 line = line[:-1]
774 775 l, f, b, c = line.split(b'\0')
775 776 backupentries.append((l, f, b, bool(c)))
776 777
777 778 _playback(
778 779 file,
779 780 report,
780 781 opener,
781 782 vfsmap,
782 783 entries,
783 784 backupentries,
784 785 checkambigfiles=checkambigfiles,
785 786 )
@@ -1,1425 +1,1434 b''
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 > from mercurial import pycompat
7 7 > def showargs(ui, repo, hooktype, **kwargs):
8 8 > kwargs = pycompat.byteskwargs(kwargs)
9 9 > ui.write(b'%s Python hook: %s\n' % (hooktype,
10 10 > b','.join(sorted(kwargs))))
11 11 > EOF
12 12
13 13 $ hg init a
14 14 $ cd a
15 15 $ cat > .hg/hgrc <<EOF
16 16 > [hooks]
17 17 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py --line commit"
18 18 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py --line commit.b"
19 19 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py --line precommit"
20 20 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py --line pretxncommit"
21 21 > pretxncommit.tip = hg -q tip
22 22 > pre-identify = sh -c "printenv.py --line pre-identify 1"
23 23 > pre-cat = sh -c "printenv.py --line pre-cat"
24 24 > post-cat = sh -c "printenv.py --line post-cat"
25 25 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py --line pretxnopen"
26 26 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py --line pretxnclose"
27 27 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py --line txnclose"
28 28 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
29 29 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py --line txnabort"
30 30 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
31 31 > EOF
32 32 $ echo a > a
33 33 $ hg add a
34 34 $ hg commit -m a
35 35 precommit hook: HG_HOOKNAME=precommit
36 36 HG_HOOKTYPE=precommit
37 37 HG_PARENT1=0000000000000000000000000000000000000000
38 38
39 39 pretxnopen hook: HG_HOOKNAME=pretxnopen
40 40 HG_HOOKTYPE=pretxnopen
41 41 HG_TXNID=TXN:$ID$
42 42 HG_TXNNAME=commit
43 43
44 44 pretxncommit hook: HG_HOOKNAME=pretxncommit
45 45 HG_HOOKTYPE=pretxncommit
46 46 HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
47 47 HG_PARENT1=0000000000000000000000000000000000000000
48 48 HG_PENDING=$TESTTMP/a
49 49
50 50 0:cb9a9f314b8b
51 51 pretxnclose hook: HG_HOOKNAME=pretxnclose
52 52 HG_HOOKTYPE=pretxnclose
53 53 HG_PENDING=$TESTTMP/a
54 54 HG_PHASES_MOVED=1
55 55 HG_TXNID=TXN:$ID$
56 56 HG_TXNNAME=commit
57 57
58 58 txnclose hook: HG_HOOKNAME=txnclose
59 59 HG_HOOKTYPE=txnclose
60 60 HG_PHASES_MOVED=1
61 61 HG_TXNID=TXN:$ID$
62 62 HG_TXNNAME=commit
63 63
64 64 commit hook: HG_HOOKNAME=commit
65 65 HG_HOOKTYPE=commit
66 66 HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 67 HG_PARENT1=0000000000000000000000000000000000000000
68 68
69 69 commit.b hook: HG_HOOKNAME=commit.b
70 70 HG_HOOKTYPE=commit
71 71 HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
72 72 HG_PARENT1=0000000000000000000000000000000000000000
73 73
74 74
75 75 $ hg clone . ../b
76 76 updating to branch default
77 77 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 $ cd ../b
79 79
80 80 changegroup hooks can see env vars
81 81
82 82 $ cat > .hg/hgrc <<EOF
83 83 > [hooks]
84 84 > prechangegroup = sh -c "printenv.py --line prechangegroup"
85 85 > changegroup = sh -c "printenv.py --line changegroup"
86 86 > incoming = sh -c "printenv.py --line incoming"
87 87 > EOF
88 88
89 89 pretxncommit and commit hooks can see both parents of merge
90 90
91 91 $ cd ../a
92 92 $ echo b >> a
93 93 $ hg commit -m a1 -d "1 0"
94 94 precommit hook: HG_HOOKNAME=precommit
95 95 HG_HOOKTYPE=precommit
96 96 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
97 97
98 98 pretxnopen hook: HG_HOOKNAME=pretxnopen
99 99 HG_HOOKTYPE=pretxnopen
100 100 HG_TXNID=TXN:$ID$
101 101 HG_TXNNAME=commit
102 102
103 103 pretxncommit hook: HG_HOOKNAME=pretxncommit
104 104 HG_HOOKTYPE=pretxncommit
105 105 HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd
106 106 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
107 107 HG_PENDING=$TESTTMP/a
108 108
109 109 1:ab228980c14d
110 110 pretxnclose hook: HG_HOOKNAME=pretxnclose
111 111 HG_HOOKTYPE=pretxnclose
112 112 HG_PENDING=$TESTTMP/a
113 113 HG_TXNID=TXN:$ID$
114 114 HG_TXNNAME=commit
115 115
116 116 txnclose hook: HG_HOOKNAME=txnclose
117 117 HG_HOOKTYPE=txnclose
118 118 HG_TXNID=TXN:$ID$
119 119 HG_TXNNAME=commit
120 120
121 121 commit hook: HG_HOOKNAME=commit
122 122 HG_HOOKTYPE=commit
123 123 HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd
124 124 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
125 125
126 126 commit.b hook: HG_HOOKNAME=commit.b
127 127 HG_HOOKTYPE=commit
128 128 HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd
129 129 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
130 130
131 131 $ hg update -C 0
132 132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133 $ echo b > b
134 134 $ hg add b
135 135 $ hg commit -m b -d '1 0'
136 136 precommit hook: HG_HOOKNAME=precommit
137 137 HG_HOOKTYPE=precommit
138 138 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
139 139
140 140 pretxnopen hook: HG_HOOKNAME=pretxnopen
141 141 HG_HOOKTYPE=pretxnopen
142 142 HG_TXNID=TXN:$ID$
143 143 HG_TXNNAME=commit
144 144
145 145 pretxncommit hook: HG_HOOKNAME=pretxncommit
146 146 HG_HOOKTYPE=pretxncommit
147 147 HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
148 148 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
149 149 HG_PENDING=$TESTTMP/a
150 150
151 151 2:ee9deb46ab31
152 152 pretxnclose hook: HG_HOOKNAME=pretxnclose
153 153 HG_HOOKTYPE=pretxnclose
154 154 HG_PENDING=$TESTTMP/a
155 155 HG_TXNID=TXN:$ID$
156 156 HG_TXNNAME=commit
157 157
158 158 created new head
159 159 txnclose hook: HG_HOOKNAME=txnclose
160 160 HG_HOOKTYPE=txnclose
161 161 HG_TXNID=TXN:$ID$
162 162 HG_TXNNAME=commit
163 163
164 164 commit hook: HG_HOOKNAME=commit
165 165 HG_HOOKTYPE=commit
166 166 HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
167 167 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
168 168
169 169 commit.b hook: HG_HOOKNAME=commit.b
170 170 HG_HOOKTYPE=commit
171 171 HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
172 172 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
173 173
174 174 $ hg merge 1
175 175 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 176 (branch merge, don't forget to commit)
177 177 $ hg commit -m merge -d '2 0'
178 178 precommit hook: HG_HOOKNAME=precommit
179 179 HG_HOOKTYPE=precommit
180 180 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
181 181 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
182 182
183 183 pretxnopen hook: HG_HOOKNAME=pretxnopen
184 184 HG_HOOKTYPE=pretxnopen
185 185 HG_TXNID=TXN:$ID$
186 186 HG_TXNNAME=commit
187 187
188 188 pretxncommit hook: HG_HOOKNAME=pretxncommit
189 189 HG_HOOKTYPE=pretxncommit
190 190 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
191 191 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
192 192 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
193 193 HG_PENDING=$TESTTMP/a
194 194
195 195 3:07f3376c1e65
196 196 pretxnclose hook: HG_HOOKNAME=pretxnclose
197 197 HG_HOOKTYPE=pretxnclose
198 198 HG_PENDING=$TESTTMP/a
199 199 HG_TXNID=TXN:$ID$
200 200 HG_TXNNAME=commit
201 201
202 202 txnclose hook: HG_HOOKNAME=txnclose
203 203 HG_HOOKTYPE=txnclose
204 204 HG_TXNID=TXN:$ID$
205 205 HG_TXNNAME=commit
206 206
207 207 commit hook: HG_HOOKNAME=commit
208 208 HG_HOOKTYPE=commit
209 209 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
210 210 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
211 211 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
212 212
213 213 commit.b hook: HG_HOOKNAME=commit.b
214 214 HG_HOOKTYPE=commit
215 215 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
216 216 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
217 217 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
218 218
219 219
220 220 test generic hooks
221 221
222 222 $ hg id
223 223 pre-identify hook: HG_ARGS=id
224 224 HG_HOOKNAME=pre-identify
225 225 HG_HOOKTYPE=pre-identify
226 226 HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None, 'template': ''}
227 227 HG_PATS=[]
228 228
229 229 abort: pre-identify hook exited with status 1
230 230 [40]
231 231 $ hg cat b
232 232 pre-cat hook: HG_ARGS=cat b
233 233 HG_HOOKNAME=pre-cat
234 234 HG_HOOKTYPE=pre-cat
235 235 HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''}
236 236 HG_PATS=['b']
237 237
238 238 b
239 239 post-cat hook: HG_ARGS=cat b
240 240 HG_HOOKNAME=post-cat
241 241 HG_HOOKTYPE=post-cat
242 242 HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''}
243 243 HG_PATS=['b']
244 244 HG_RESULT=0
245 245
246 246
247 247 $ cd ../b
248 248 $ hg pull ../a
249 249 pulling from ../a
250 250 searching for changes
251 251 prechangegroup hook: HG_HOOKNAME=prechangegroup
252 252 HG_HOOKTYPE=prechangegroup
253 253 HG_SOURCE=pull
254 254 HG_TXNID=TXN:$ID$
255 255 HG_TXNNAME=pull
256 256 file:/*/$TESTTMP/a (glob)
257 257 HG_URL=file:$TESTTMP/a
258 258
259 259 adding changesets
260 260 adding manifests
261 261 adding file changes
262 262 added 3 changesets with 2 changes to 2 files
263 263 new changesets ab228980c14d:07f3376c1e65
264 264 changegroup hook: HG_HOOKNAME=changegroup
265 265 HG_HOOKTYPE=changegroup
266 266 HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd
267 267 HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2
268 268 HG_SOURCE=pull
269 269 HG_TXNID=TXN:$ID$
270 270 HG_TXNNAME=pull
271 271 file:/*/$TESTTMP/a (glob)
272 272 HG_URL=file:$TESTTMP/a
273 273
274 274 incoming hook: HG_HOOKNAME=incoming
275 275 HG_HOOKTYPE=incoming
276 276 HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd
277 277 HG_SOURCE=pull
278 278 HG_TXNID=TXN:$ID$
279 279 HG_TXNNAME=pull
280 280 file:/*/$TESTTMP/a (glob)
281 281 HG_URL=file:$TESTTMP/a
282 282
283 283 incoming hook: HG_HOOKNAME=incoming
284 284 HG_HOOKTYPE=incoming
285 285 HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
286 286 HG_SOURCE=pull
287 287 HG_TXNID=TXN:$ID$
288 288 HG_TXNNAME=pull
289 289 file:/*/$TESTTMP/a (glob)
290 290 HG_URL=file:$TESTTMP/a
291 291
292 292 incoming hook: HG_HOOKNAME=incoming
293 293 HG_HOOKTYPE=incoming
294 294 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
295 295 HG_SOURCE=pull
296 296 HG_TXNID=TXN:$ID$
297 297 HG_TXNNAME=pull
298 298 file:/*/$TESTTMP/a (glob)
299 299 HG_URL=file:$TESTTMP/a
300 300
301 301 (run 'hg update' to get a working copy)
302 302
303 303 tag hooks can see env vars
304 304
305 305 $ cd ../a
306 306 $ cat >> .hg/hgrc <<EOF
307 307 > pretag = sh -c "printenv.py --line pretag"
308 308 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py --line tag"
309 309 > EOF
310 310 $ hg tag -d '3 0' a
311 311 pretag hook: HG_HOOKNAME=pretag
312 312 HG_HOOKTYPE=pretag
313 313 HG_LOCAL=0
314 314 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
315 315 HG_TAG=a
316 316
317 317 precommit hook: HG_HOOKNAME=precommit
318 318 HG_HOOKTYPE=precommit
319 319 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
320 320
321 321 pretxnopen hook: HG_HOOKNAME=pretxnopen
322 322 HG_HOOKTYPE=pretxnopen
323 323 HG_TXNID=TXN:$ID$
324 324 HG_TXNNAME=commit
325 325
326 326 pretxncommit hook: HG_HOOKNAME=pretxncommit
327 327 HG_HOOKTYPE=pretxncommit
328 328 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
329 329 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
330 330 HG_PENDING=$TESTTMP/a
331 331
332 332 4:539e4b31b6dc
333 333 pretxnclose hook: HG_HOOKNAME=pretxnclose
334 334 HG_HOOKTYPE=pretxnclose
335 335 HG_PENDING=$TESTTMP/a
336 336 HG_TXNID=TXN:$ID$
337 337 HG_TXNNAME=commit
338 338
339 339 tag hook: HG_HOOKNAME=tag
340 340 HG_HOOKTYPE=tag
341 341 HG_LOCAL=0
342 342 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2
343 343 HG_TAG=a
344 344
345 345 txnclose hook: HG_HOOKNAME=txnclose
346 346 HG_HOOKTYPE=txnclose
347 347 HG_TXNID=TXN:$ID$
348 348 HG_TXNNAME=commit
349 349
350 350 commit hook: HG_HOOKNAME=commit
351 351 HG_HOOKTYPE=commit
352 352 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
353 353 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
354 354
355 355 commit.b hook: HG_HOOKNAME=commit.b
356 356 HG_HOOKTYPE=commit
357 357 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
358 358 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
359 359
360 360 $ hg tag -l la
361 361 pretag hook: HG_HOOKNAME=pretag
362 362 HG_HOOKTYPE=pretag
363 363 HG_LOCAL=1
364 364 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
365 365 HG_TAG=la
366 366
367 367 tag hook: HG_HOOKNAME=tag
368 368 HG_HOOKTYPE=tag
369 369 HG_LOCAL=1
370 370 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
371 371 HG_TAG=la
372 372
373 373
374 374 pretag hook can forbid tagging
375 375
376 376 $ cat >> .hg/hgrc <<EOF
377 377 > pretag.forbid = sh -c "printenv.py --line pretag.forbid 1"
378 378 > EOF
379 379 $ hg tag -d '4 0' fa
380 380 pretag hook: HG_HOOKNAME=pretag
381 381 HG_HOOKTYPE=pretag
382 382 HG_LOCAL=0
383 383 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
384 384 HG_TAG=fa
385 385
386 386 pretag.forbid hook: HG_HOOKNAME=pretag.forbid
387 387 HG_HOOKTYPE=pretag
388 388 HG_LOCAL=0
389 389 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
390 390 HG_TAG=fa
391 391
392 392 abort: pretag.forbid hook exited with status 1
393 393 [40]
394 394 $ hg tag -l fla
395 395 pretag hook: HG_HOOKNAME=pretag
396 396 HG_HOOKTYPE=pretag
397 397 HG_LOCAL=1
398 398 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
399 399 HG_TAG=fla
400 400
401 401 pretag.forbid hook: HG_HOOKNAME=pretag.forbid
402 402 HG_HOOKTYPE=pretag
403 403 HG_LOCAL=1
404 404 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
405 405 HG_TAG=fla
406 406
407 407 abort: pretag.forbid hook exited with status 1
408 408 [40]
409 409
410 410 pretxncommit hook can see changeset, can roll back txn, changeset no
411 411 more there after
412 412
413 413 $ cat >> .hg/hgrc <<EOF
414 414 > pretxncommit.forbid0 = sh -c "hg tip -q"
415 415 > pretxncommit.forbid1 = sh -c "printenv.py --line pretxncommit.forbid 1"
416 416 > EOF
417 417 $ echo z > z
418 418 $ hg add z
419 419 $ hg -q tip
420 420 4:539e4b31b6dc
421 421 $ hg commit -m 'fail' -d '4 0'
422 422 precommit hook: HG_HOOKNAME=precommit
423 423 HG_HOOKTYPE=precommit
424 424 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
425 425
426 426 pretxnopen hook: HG_HOOKNAME=pretxnopen
427 427 HG_HOOKTYPE=pretxnopen
428 428 HG_TXNID=TXN:$ID$
429 429 HG_TXNNAME=commit
430 430
431 431 pretxncommit hook: HG_HOOKNAME=pretxncommit
432 432 HG_HOOKTYPE=pretxncommit
433 433 HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567
434 434 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
435 435 HG_PENDING=$TESTTMP/a
436 436
437 437 5:6f611f8018c1
438 438 5:6f611f8018c1
439 439 pretxncommit.forbid hook: HG_HOOKNAME=pretxncommit.forbid1
440 440 HG_HOOKTYPE=pretxncommit
441 441 HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567
442 442 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
443 443 HG_PENDING=$TESTTMP/a
444 444
445 445 transaction abort!
446 446 txnabort Python hook: changes,txnid,txnname
447 447 txnabort hook: HG_HOOKNAME=txnabort.1
448 448 HG_HOOKTYPE=txnabort
449 449 HG_TXNID=TXN:$ID$
450 450 HG_TXNNAME=commit
451 451
452 452 rollback completed
453 453 abort: pretxncommit.forbid1 hook exited with status 1
454 454 [40]
455 455 $ hg -q tip
456 456 4:539e4b31b6dc
457 457
458 458 (Check that no 'changelog.i.a' file were left behind)
459 459
460 460 $ ls -1 .hg/store/
461 461 00changelog.i
462 462 00manifest.i
463 463 data
464 464 fncache (repofncache !)
465 465 journal.phaseroots
466 466 phaseroots
467 467 requires
468 468 undo
469 469 undo.backup.fncache (repofncache !)
470 470 undo.backupfiles
471 471 undo.phaseroots
472 472
473 473
474 474 precommit hook can prevent commit
475 475
476 476 $ cat >> .hg/hgrc <<EOF
477 477 > precommit.forbid = sh -c "printenv.py --line precommit.forbid 1"
478 478 > EOF
479 479 $ hg commit -m 'fail' -d '4 0'
480 480 precommit hook: HG_HOOKNAME=precommit
481 481 HG_HOOKTYPE=precommit
482 482 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
483 483
484 484 precommit.forbid hook: HG_HOOKNAME=precommit.forbid
485 485 HG_HOOKTYPE=precommit
486 486 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
487 487
488 488 abort: precommit.forbid hook exited with status 1
489 489 [40]
490 490 $ hg -q tip
491 491 4:539e4b31b6dc
492 492
493 493 preupdate hook can prevent update
494 494
495 495 $ cat >> .hg/hgrc <<EOF
496 496 > preupdate = sh -c "printenv.py --line preupdate"
497 497 > EOF
498 498 $ hg update 1
499 499 preupdate hook: HG_HOOKNAME=preupdate
500 500 HG_HOOKTYPE=preupdate
501 501 HG_PARENT1=ab228980c14d
502 502
503 503 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
504 504
505 505 update hook
506 506
507 507 $ cat >> .hg/hgrc <<EOF
508 508 > update = sh -c "printenv.py --line update"
509 509 > EOF
510 510 $ hg update
511 511 preupdate hook: HG_HOOKNAME=preupdate
512 512 HG_HOOKTYPE=preupdate
513 513 HG_PARENT1=539e4b31b6dc
514 514
515 515 update hook: HG_ERROR=0
516 516 HG_HOOKNAME=update
517 517 HG_HOOKTYPE=update
518 518 HG_PARENT1=539e4b31b6dc
519 519
520 520 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
521 521
522 522 pushkey hook
523 523
524 524 $ cat >> .hg/hgrc <<EOF
525 525 > pushkey = sh -c "printenv.py --line pushkey"
526 526 > EOF
527 527 $ cd ../b
528 528 $ hg bookmark -r null foo
529 529 $ hg push -B foo ../a
530 530 pushing to ../a
531 531 searching for changes
532 532 no changes found
533 533 pretxnopen hook: HG_HOOKNAME=pretxnopen
534 534 HG_HOOKTYPE=pretxnopen
535 535 HG_TXNID=TXN:$ID$
536 536 HG_TXNNAME=push
537 537
538 538 pretxnclose hook: HG_BOOKMARK_MOVED=1
539 539 HG_BUNDLE2=1
540 540 HG_HOOKNAME=pretxnclose
541 541 HG_HOOKTYPE=pretxnclose
542 542 HG_PENDING=$TESTTMP/a
543 543 HG_SOURCE=push
544 544 HG_TXNID=TXN:$ID$
545 545 HG_TXNNAME=push
546 546 HG_URL=file:$TESTTMP/a
547 547
548 548 pushkey hook: HG_BUNDLE2=1
549 549 HG_HOOKNAME=pushkey
550 550 HG_HOOKTYPE=pushkey
551 551 HG_KEY=foo
552 552 HG_NAMESPACE=bookmarks
553 553 HG_NEW=0000000000000000000000000000000000000000
554 554 HG_PUSHKEYCOMPAT=1
555 555 HG_SOURCE=push
556 556 HG_TXNID=TXN:$ID$
557 557 HG_TXNNAME=push
558 558 HG_URL=file:$TESTTMP/a
559 559
560 560 txnclose hook: HG_BOOKMARK_MOVED=1
561 561 HG_BUNDLE2=1
562 562 HG_HOOKNAME=txnclose
563 563 HG_HOOKTYPE=txnclose
564 564 HG_SOURCE=push
565 565 HG_TXNID=TXN:$ID$
566 566 HG_TXNNAME=push
567 567 HG_URL=file:$TESTTMP/a
568 568
569 569 exporting bookmark foo
570 570 [1]
571 571 $ cd ../a
572 572
573 573 listkeys hook
574 574
575 575 $ cat >> .hg/hgrc <<EOF
576 576 > listkeys = sh -c "printenv.py --line listkeys"
577 577 > EOF
578 578 $ hg bookmark -r null bar
579 579 pretxnopen hook: HG_HOOKNAME=pretxnopen
580 580 HG_HOOKTYPE=pretxnopen
581 581 HG_TXNID=TXN:$ID$
582 582 HG_TXNNAME=bookmark
583 583
584 584 pretxnclose hook: HG_BOOKMARK_MOVED=1
585 585 HG_HOOKNAME=pretxnclose
586 586 HG_HOOKTYPE=pretxnclose
587 587 HG_PENDING=$TESTTMP/a
588 588 HG_TXNID=TXN:$ID$
589 589 HG_TXNNAME=bookmark
590 590
591 591 txnclose hook: HG_BOOKMARK_MOVED=1
592 592 HG_HOOKNAME=txnclose
593 593 HG_HOOKTYPE=txnclose
594 594 HG_TXNID=TXN:$ID$
595 595 HG_TXNNAME=bookmark
596 596
597 597 $ cd ../b
598 598 $ hg pull -B bar ../a
599 599 pulling from ../a
600 600 listkeys hook: HG_HOOKNAME=listkeys
601 601 HG_HOOKTYPE=listkeys
602 602 HG_NAMESPACE=bookmarks
603 603 HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
604 604
605 605 no changes found
606 606 adding remote bookmark bar
607 607 $ cd ../a
608 608
609 609 test that prepushkey can prevent incoming keys
610 610
611 611 $ cat >> .hg/hgrc <<EOF
612 612 > prepushkey = sh -c "printenv.py --line prepushkey.forbid 1"
613 613 > EOF
614 614 $ cd ../b
615 615 $ hg bookmark -r null baz
616 616 $ hg push -B baz ../a
617 617 pushing to ../a
618 618 searching for changes
619 619 listkeys hook: HG_HOOKNAME=listkeys
620 620 HG_HOOKTYPE=listkeys
621 621 HG_NAMESPACE=phases
622 622 HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
623 623
624 624 listkeys hook: HG_HOOKNAME=listkeys
625 625 HG_HOOKTYPE=listkeys
626 626 HG_NAMESPACE=bookmarks
627 627 HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
628 628
629 629 no changes found
630 630 pretxnopen hook: HG_HOOKNAME=pretxnopen
631 631 HG_HOOKTYPE=pretxnopen
632 632 HG_TXNID=TXN:$ID$
633 633 HG_TXNNAME=push
634 634
635 635 prepushkey.forbid hook: HG_BUNDLE2=1
636 636 HG_HOOKNAME=prepushkey
637 637 HG_HOOKTYPE=prepushkey
638 638 HG_KEY=baz
639 639 HG_NAMESPACE=bookmarks
640 640 HG_NEW=0000000000000000000000000000000000000000
641 641 HG_PUSHKEYCOMPAT=1
642 642 HG_SOURCE=push
643 643 HG_TXNID=TXN:$ID$
644 644 HG_TXNNAME=push
645 645 HG_URL=file:$TESTTMP/a
646 646
647 txnabort Python hook: bundle2,changes,source,txnid,txnname,url
648 txnabort hook: HG_BUNDLE2=1
649 HG_HOOKNAME=txnabort.1
650 HG_HOOKTYPE=txnabort
651 HG_SOURCE=push
652 HG_TXNID=TXN:$ID$
653 HG_TXNNAME=push
654 HG_URL=file:$TESTTMP/a
655
647 656 abort: prepushkey hook exited with status 1
648 657 [40]
649 658 $ cd ../a
650 659
651 660 test that prelistkeys can prevent listing keys
652 661
653 662 $ cat >> .hg/hgrc <<EOF
654 663 > prelistkeys = sh -c "printenv.py --line prelistkeys.forbid 1"
655 664 > EOF
656 665 $ hg bookmark -r null quux
657 666 pretxnopen hook: HG_HOOKNAME=pretxnopen
658 667 HG_HOOKTYPE=pretxnopen
659 668 HG_TXNID=TXN:$ID$
660 669 HG_TXNNAME=bookmark
661 670
662 671 pretxnclose hook: HG_BOOKMARK_MOVED=1
663 672 HG_HOOKNAME=pretxnclose
664 673 HG_HOOKTYPE=pretxnclose
665 674 HG_PENDING=$TESTTMP/a
666 675 HG_TXNID=TXN:$ID$
667 676 HG_TXNNAME=bookmark
668 677
669 678 txnclose hook: HG_BOOKMARK_MOVED=1
670 679 HG_HOOKNAME=txnclose
671 680 HG_HOOKTYPE=txnclose
672 681 HG_TXNID=TXN:$ID$
673 682 HG_TXNNAME=bookmark
674 683
675 684 $ cd ../b
676 685 $ hg pull -B quux ../a
677 686 pulling from ../a
678 687 prelistkeys.forbid hook: HG_HOOKNAME=prelistkeys
679 688 HG_HOOKTYPE=prelistkeys
680 689 HG_NAMESPACE=bookmarks
681 690
682 691 abort: prelistkeys hook exited with status 1
683 692 [40]
684 693 $ cd ../a
685 694 $ rm .hg/hgrc
686 695
687 696 prechangegroup hook can prevent incoming changes
688 697
689 698 $ cd ../b
690 699 $ hg -q tip
691 700 3:07f3376c1e65
692 701 $ cat > .hg/hgrc <<EOF
693 702 > [hooks]
694 703 > prechangegroup.forbid = sh -c "printenv.py --line prechangegroup.forbid 1"
695 704 > EOF
696 705 $ hg pull ../a
697 706 pulling from ../a
698 707 searching for changes
699 708 prechangegroup.forbid hook: HG_HOOKNAME=prechangegroup.forbid
700 709 HG_HOOKTYPE=prechangegroup
701 710 HG_SOURCE=pull
702 711 HG_TXNID=TXN:$ID$
703 712 HG_TXNNAME=pull
704 713 file:/*/$TESTTMP/a (glob)
705 714 HG_URL=file:$TESTTMP/a
706 715
707 716 abort: prechangegroup.forbid hook exited with status 1
708 717 [40]
709 718
710 719 pretxnchangegroup hook can see incoming changes, can roll back txn,
711 720 incoming changes no longer there after
712 721
713 722 $ cat > .hg/hgrc <<EOF
714 723 > [hooks]
715 724 > pretxnchangegroup.forbid0 = hg tip -q
716 725 > pretxnchangegroup.forbid1 = sh -c "printenv.py --line pretxnchangegroup.forbid 1"
717 726 > EOF
718 727 $ hg pull ../a
719 728 pulling from ../a
720 729 searching for changes
721 730 adding changesets
722 731 adding manifests
723 732 adding file changes
724 733 4:539e4b31b6dc
725 734 pretxnchangegroup.forbid hook: HG_HOOKNAME=pretxnchangegroup.forbid1
726 735 HG_HOOKTYPE=pretxnchangegroup
727 736 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
728 737 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
729 738 HG_PENDING=$TESTTMP/b
730 739 HG_SOURCE=pull
731 740 HG_TXNID=TXN:$ID$
732 741 HG_TXNNAME=pull
733 742 file:/*/$TESTTMP/a (glob)
734 743 HG_URL=file:$TESTTMP/a
735 744
736 745 transaction abort!
737 746 rollback completed
738 747 abort: pretxnchangegroup.forbid1 hook exited with status 1
739 748 [40]
740 749 $ hg -q tip
741 750 3:07f3376c1e65
742 751
743 752 outgoing hooks can see env vars
744 753
745 754 $ rm .hg/hgrc
746 755 $ cat > ../a/.hg/hgrc <<EOF
747 756 > [hooks]
748 757 > preoutgoing = sh -c "printenv.py --line preoutgoing"
749 758 > outgoing = sh -c "printenv.py --line outgoing"
750 759 > EOF
751 760 $ hg pull ../a
752 761 pulling from ../a
753 762 searching for changes
754 763 preoutgoing hook: HG_HOOKNAME=preoutgoing
755 764 HG_HOOKTYPE=preoutgoing
756 765 HG_SOURCE=pull
757 766
758 767 outgoing hook: HG_HOOKNAME=outgoing
759 768 HG_HOOKTYPE=outgoing
760 769 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
761 770 HG_SOURCE=pull
762 771
763 772 adding changesets
764 773 adding manifests
765 774 adding file changes
766 775 adding remote bookmark quux
767 776 added 1 changesets with 1 changes to 1 files
768 777 new changesets 539e4b31b6dc
769 778 (run 'hg update' to get a working copy)
770 779 $ hg rollback
771 780 repository tip rolled back to revision 3 (undo pull)
772 781
773 782 preoutgoing hook can prevent outgoing changes
774 783
775 784 $ cat >> ../a/.hg/hgrc <<EOF
776 785 > preoutgoing.forbid = sh -c "printenv.py --line preoutgoing.forbid 1"
777 786 > EOF
778 787 $ hg pull ../a
779 788 pulling from ../a
780 789 searching for changes
781 790 preoutgoing hook: HG_HOOKNAME=preoutgoing
782 791 HG_HOOKTYPE=preoutgoing
783 792 HG_SOURCE=pull
784 793
785 794 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid
786 795 HG_HOOKTYPE=preoutgoing
787 796 HG_SOURCE=pull
788 797
789 798 abort: preoutgoing.forbid hook exited with status 1
790 799 [40]
791 800
792 801 outgoing hooks work for local clones
793 802
794 803 $ cd ..
795 804 $ cat > a/.hg/hgrc <<EOF
796 805 > [hooks]
797 806 > preoutgoing = sh -c "printenv.py --line preoutgoing"
798 807 > outgoing = sh -c "printenv.py --line outgoing"
799 808 > EOF
800 809 $ hg clone a c
801 810 preoutgoing hook: HG_HOOKNAME=preoutgoing
802 811 HG_HOOKTYPE=preoutgoing
803 812 HG_SOURCE=clone
804 813
805 814 outgoing hook: HG_HOOKNAME=outgoing
806 815 HG_HOOKTYPE=outgoing
807 816 HG_NODE=0000000000000000000000000000000000000000
808 817 HG_SOURCE=clone
809 818
810 819 updating to branch default
811 820 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
812 821 $ rm -rf c
813 822
814 823 preoutgoing hook can prevent outgoing changes for local clones
815 824
816 825 $ cat >> a/.hg/hgrc <<EOF
817 826 > preoutgoing.forbid = sh -c "printenv.py --line preoutgoing.forbid 1"
818 827 > EOF
819 828 $ hg clone a zzz
820 829 preoutgoing hook: HG_HOOKNAME=preoutgoing
821 830 HG_HOOKTYPE=preoutgoing
822 831 HG_SOURCE=clone
823 832
824 833 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid
825 834 HG_HOOKTYPE=preoutgoing
826 835 HG_SOURCE=clone
827 836
828 837 abort: preoutgoing.forbid hook exited with status 1
829 838 [40]
830 839
831 840 $ cd "$TESTTMP/b"
832 841
833 842 $ cat > hooktests.py <<EOF
834 843 > from mercurial import (
835 844 > error,
836 845 > pycompat,
837 846 > )
838 847 >
839 848 > uncallable = 0
840 849 >
841 850 > def printargs(ui, args):
842 851 > a = list(pycompat.byteskwargs(args).items())
843 852 > a.sort()
844 853 > ui.write(b'hook args:\n')
845 854 > for k, v in a:
846 855 > ui.write(b' %s %s\n' % (k, v))
847 856 >
848 857 > def passhook(ui, repo, **args):
849 858 > printargs(ui, args)
850 859 >
851 860 > def failhook(ui, repo, **args):
852 861 > printargs(ui, args)
853 862 > return True
854 863 >
855 864 > class LocalException(Exception):
856 865 > pass
857 866 >
858 867 > def raisehook(**args):
859 868 > raise LocalException('exception from hook')
860 869 >
861 870 > def aborthook(**args):
862 871 > raise error.Abort(b'raise abort from hook')
863 872 >
864 873 > def brokenhook(**args):
865 874 > return 1 + {}
866 875 >
867 876 > def verbosehook(ui, **args):
868 877 > ui.note(b'verbose output from hook\n')
869 878 >
870 879 > def printtags(ui, repo, **args):
871 880 > ui.write(b'[%s]\n' % b', '.join(sorted(repo.tags())))
872 881 >
873 882 > class container(object):
874 883 > unreachable = 1
875 884 > EOF
876 885
877 886 $ cat > syntaxerror.py << NO_CHECK_EOF
878 887 > (foo
879 888 > NO_CHECK_EOF
880 889
881 890 test python hooks
882 891
883 892 #if windows
884 893 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
885 894 #else
886 895 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
887 896 #endif
888 897 $ export PYTHONPATH
889 898
890 899 $ echo '[hooks]' > ../a/.hg/hgrc
891 900 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
892 901 $ hg pull ../a 2>&1 | grep 'raised an exception'
893 902 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
894 903
895 904 $ echo '[hooks]' > ../a/.hg/hgrc
896 905 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
897 906 $ hg pull ../a 2>&1 | grep 'raised an exception'
898 907 error: preoutgoing.raise hook raised an exception: exception from hook
899 908
900 909 $ echo '[hooks]' > ../a/.hg/hgrc
901 910 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
902 911 $ hg pull ../a
903 912 pulling from ../a
904 913 searching for changes
905 914 error: preoutgoing.abort hook failed: raise abort from hook
906 915 abort: raise abort from hook
907 916 [255]
908 917
909 918 $ echo '[hooks]' > ../a/.hg/hgrc
910 919 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
911 920 $ hg pull ../a
912 921 pulling from ../a
913 922 searching for changes
914 923 hook args:
915 924 hooktype preoutgoing
916 925 source pull
917 926 abort: preoutgoing.fail hook failed
918 927 [40]
919 928
920 929 $ echo '[hooks]' > ../a/.hg/hgrc
921 930 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
922 931 $ hg pull ../a
923 932 pulling from ../a
924 933 searching for changes
925 934 abort: preoutgoing.uncallable hook is invalid: "hooktests.uncallable" is not callable
926 935 [255]
927 936
928 937 $ echo '[hooks]' > ../a/.hg/hgrc
929 938 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
930 939 $ hg pull ../a
931 940 pulling from ../a
932 941 searching for changes
933 942 abort: preoutgoing.nohook hook is invalid: "hooktests.nohook" is not defined
934 943 [255]
935 944
936 945 $ echo '[hooks]' > ../a/.hg/hgrc
937 946 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
938 947 $ hg pull ../a
939 948 pulling from ../a
940 949 searching for changes
941 950 abort: preoutgoing.nomodule hook is invalid: "nomodule" not in a module
942 951 [255]
943 952
944 953 $ echo '[hooks]' > ../a/.hg/hgrc
945 954 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
946 955 $ hg pull ../a
947 956 pulling from ../a
948 957 searching for changes
949 958 abort: preoutgoing.badmodule hook is invalid: import of "nomodule" failed
950 959 (run with --traceback for stack trace)
951 960 [255]
952 961
953 962 $ echo '[hooks]' > ../a/.hg/hgrc
954 963 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
955 964 $ hg pull ../a
956 965 pulling from ../a
957 966 searching for changes
958 967 abort: preoutgoing.unreachable hook is invalid: import of "hooktests.container" failed
959 968 (run with --traceback for stack trace)
960 969 [255]
961 970
962 971 $ echo '[hooks]' > ../a/.hg/hgrc
963 972 $ echo 'preoutgoing.syntaxerror = python:syntaxerror.syntaxerror' >> ../a/.hg/hgrc
964 973 $ hg pull ../a
965 974 pulling from ../a
966 975 searching for changes
967 976 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
968 977 (run with --traceback for stack trace)
969 978 [255]
970 979
971 980 $ hg pull ../a --traceback 2>&1 | egrep 'pulling|searching|^exception|Traceback|SyntaxError|ImportError|ModuleNotFoundError|HookLoadError|abort'
972 981 pulling from ../a
973 982 searching for changes
974 983 exception from first failed import attempt:
975 984 Traceback (most recent call last):
976 985 SyntaxError: * (glob)
977 986 exception from second failed import attempt:
978 987 Traceback (most recent call last):
979 988 SyntaxError: * (glob)
980 989 Traceback (most recent call last):
981 990 ImportError: No module named 'hgext_syntaxerror' (no-py36 !)
982 991 ModuleNotFoundError: No module named 'hgext_syntaxerror' (py36 !)
983 992 Traceback (most recent call last):
984 993 SyntaxError: * (glob)
985 994 Traceback (most recent call last):
986 995 ImportError: No module named 'hgext_syntaxerror' (no-py36 !)
987 996 ModuleNotFoundError: No module named 'hgext_syntaxerror' (py36 !)
988 997 Traceback (most recent call last):
989 998 raise error.HookLoadError( (py38 !)
990 999 mercurial.error.HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
991 1000 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
992 1001
993 1002 $ echo '[hooks]' > ../a/.hg/hgrc
994 1003 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
995 1004 $ hg pull ../a
996 1005 pulling from ../a
997 1006 searching for changes
998 1007 hook args:
999 1008 hooktype preoutgoing
1000 1009 source pull
1001 1010 adding changesets
1002 1011 adding manifests
1003 1012 adding file changes
1004 1013 adding remote bookmark quux
1005 1014 added 1 changesets with 1 changes to 1 files
1006 1015 new changesets 539e4b31b6dc
1007 1016 (run 'hg update' to get a working copy)
1008 1017
1009 1018 post- python hooks that fail to *run* don't cause an abort
1010 1019 $ rm ../a/.hg/hgrc
1011 1020 $ echo '[hooks]' > .hg/hgrc
1012 1021 $ echo 'post-pull.broken = python:hooktests.brokenhook' >> .hg/hgrc
1013 1022 $ hg pull ../a
1014 1023 pulling from ../a
1015 1024 searching for changes
1016 1025 no changes found
1017 1026 error: post-pull.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
1018 1027 (run with --traceback for stack trace)
1019 1028
1020 1029 but post- python hooks that fail to *load* do
1021 1030 $ echo '[hooks]' > .hg/hgrc
1022 1031 $ echo 'post-pull.nomodule = python:nomodule' >> .hg/hgrc
1023 1032 $ hg pull ../a
1024 1033 pulling from ../a
1025 1034 searching for changes
1026 1035 no changes found
1027 1036 abort: post-pull.nomodule hook is invalid: "nomodule" not in a module
1028 1037 [255]
1029 1038
1030 1039 $ echo '[hooks]' > .hg/hgrc
1031 1040 $ echo 'post-pull.badmodule = python:nomodule.nowhere' >> .hg/hgrc
1032 1041 $ hg pull ../a
1033 1042 pulling from ../a
1034 1043 searching for changes
1035 1044 no changes found
1036 1045 abort: post-pull.badmodule hook is invalid: import of "nomodule" failed
1037 1046 (run with --traceback for stack trace)
1038 1047 [255]
1039 1048
1040 1049 $ echo '[hooks]' > .hg/hgrc
1041 1050 $ echo 'post-pull.nohook = python:hooktests.nohook' >> .hg/hgrc
1042 1051 $ hg pull ../a
1043 1052 pulling from ../a
1044 1053 searching for changes
1045 1054 no changes found
1046 1055 abort: post-pull.nohook hook is invalid: "hooktests.nohook" is not defined
1047 1056 [255]
1048 1057
1049 1058 make sure --traceback works
1050 1059
1051 1060 $ echo '[hooks]' > .hg/hgrc
1052 1061 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
1053 1062
1054 1063 $ echo aa > a
1055 1064 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
1056 1065 Traceback (most recent call last):
1057 1066
1058 1067 $ cd ..
1059 1068 $ hg init c
1060 1069 $ cd c
1061 1070
1062 1071 $ cat > hookext.py <<EOF
1063 1072 > def autohook(ui, **args):
1064 1073 > ui.write(b'Automatically installed hook\n')
1065 1074 >
1066 1075 > def reposetup(ui, repo):
1067 1076 > repo.ui.setconfig(b"hooks", b"commit.auto", autohook)
1068 1077 > EOF
1069 1078 $ echo '[extensions]' >> .hg/hgrc
1070 1079 $ echo 'hookext = hookext.py' >> .hg/hgrc
1071 1080
1072 1081 $ touch foo
1073 1082 $ hg add foo
1074 1083 $ hg ci -d '0 0' -m 'add foo'
1075 1084 Automatically installed hook
1076 1085 $ echo >> foo
1077 1086 $ hg ci --debug -d '0 0' -m 'change foo'
1078 1087 committing files:
1079 1088 foo
1080 1089 committing manifest
1081 1090 committing changelog
1082 1091 updating the branch cache
1083 1092 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
1084 1093 calling hook commit.auto: hgext_hookext.autohook
1085 1094 Automatically installed hook
1086 1095
1087 1096 $ hg showconfig hooks
1088 1097 hooks.commit.auto=<function autohook at *> (glob)
1089 1098
1090 1099 test python hook configured with python:[file]:[hook] syntax
1091 1100
1092 1101 $ cd ..
1093 1102 $ mkdir d
1094 1103 $ cd d
1095 1104 $ hg init repo
1096 1105 $ mkdir hooks
1097 1106
1098 1107 $ cd hooks
1099 1108 $ cat > testhooks.py <<EOF
1100 1109 > def testhook(ui, **args):
1101 1110 > ui.write(b'hook works\n')
1102 1111 > EOF
1103 1112 $ echo '[hooks]' > ../repo/.hg/hgrc
1104 1113 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
1105 1114
1106 1115 $ cd ../repo
1107 1116 $ hg commit -d '0 0'
1108 1117 hook works
1109 1118 nothing changed
1110 1119 [1]
1111 1120
1112 1121 $ echo '[hooks]' > .hg/hgrc
1113 1122 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
1114 1123 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
1115 1124
1116 1125 $ hg up null
1117 1126 loading update.ne hook failed:
1118 1127 abort: $ENOENT$: '$TESTTMP/d/repo/nonexistent.py'
1119 1128 [255]
1120 1129
1121 1130 $ hg id
1122 1131 loading pre-identify.npmd hook failed:
1123 1132 abort: No module named 'repo'
1124 1133 [255]
1125 1134
1126 1135 $ cd ../../b
1127 1136
1128 1137 make sure --traceback works on hook import failure
1129 1138
1130 1139 $ cat > importfail.py <<EOF
1131 1140 > import somebogusmodule
1132 1141 > # dereference something in the module to force demandimport to load it
1133 1142 > somebogusmodule.whatever
1134 1143 > EOF
1135 1144
1136 1145 $ echo '[hooks]' > .hg/hgrc
1137 1146 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
1138 1147
1139 1148 $ echo a >> a
1140 1149 $ hg --traceback commit -ma 2>&1 | egrep '^exception|ImportError|ModuleNotFoundError|Traceback|HookLoadError|abort'
1141 1150 exception from first failed import attempt:
1142 1151 Traceback (most recent call last):
1143 1152 ImportError: No module named 'somebogusmodule' (no-py36 !)
1144 1153 ModuleNotFoundError: No module named 'somebogusmodule' (py36 !)
1145 1154 exception from second failed import attempt:
1146 1155 Traceback (most recent call last):
1147 1156 ImportError: No module named 'somebogusmodule' (no-py36 !)
1148 1157 ModuleNotFoundError: No module named 'somebogusmodule' (py36 !)
1149 1158 Traceback (most recent call last):
1150 1159 ImportError: No module named 'hgext_importfail' (no-py36 !)
1151 1160 ModuleNotFoundError: No module named 'hgext_importfail' (py36 !)
1152 1161 Traceback (most recent call last):
1153 1162 ImportError: No module named 'somebogusmodule' (no-py36 !)
1154 1163 ModuleNotFoundError: No module named 'somebogusmodule' (py36 !)
1155 1164 Traceback (most recent call last):
1156 1165 ImportError: No module named 'hgext_importfail' (no-py36 !)
1157 1166 ModuleNotFoundError: No module named 'hgext_importfail' (py36 !)
1158 1167 Traceback (most recent call last):
1159 1168 raise error.HookLoadError( (py38 !)
1160 1169 mercurial.error.HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
1161 1170 abort: precommit.importfail hook is invalid: import of "importfail" failed
1162 1171
1163 1172 Issue1827: Hooks Update & Commit not completely post operation
1164 1173
1165 1174 commit and update hooks should run after command completion. The largefiles
1166 1175 use demonstrates a recursive wlock, showing the hook doesn't run until the
1167 1176 final release (and dirstate flush).
1168 1177
1169 1178 $ echo '[hooks]' > .hg/hgrc
1170 1179 $ echo 'commit = hg id' >> .hg/hgrc
1171 1180 $ echo 'update = hg id' >> .hg/hgrc
1172 1181 $ echo bb > a
1173 1182 $ hg ci -ma
1174 1183 223eafe2750c tip
1175 1184 $ hg up 0 --config extensions.largefiles=
1176 1185 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
1177 1186 cb9a9f314b8b
1178 1187 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1179 1188
1180 1189 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
1181 1190 that is passed to pre/post hooks
1182 1191
1183 1192 $ echo '[hooks]' > .hg/hgrc
1184 1193 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
1185 1194 $ hg id
1186 1195 cb9a9f314b8b
1187 1196 $ hg id --verbose
1188 1197 calling hook pre-identify: hooktests.verbosehook
1189 1198 verbose output from hook
1190 1199 cb9a9f314b8b
1191 1200
1192 1201 Ensure hooks can be prioritized
1193 1202
1194 1203 $ echo '[hooks]' > .hg/hgrc
1195 1204 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
1196 1205 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
1197 1206 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
1198 1207 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
1199 1208 $ hg id --verbose
1200 1209 calling hook pre-identify.b: hooktests.verbosehook
1201 1210 verbose output from hook
1202 1211 calling hook pre-identify.a: hooktests.verbosehook
1203 1212 verbose output from hook
1204 1213 calling hook pre-identify.c: hooktests.verbosehook
1205 1214 verbose output from hook
1206 1215 cb9a9f314b8b
1207 1216
1208 1217 new tags must be visible in pretxncommit (issue3210)
1209 1218
1210 1219 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
1211 1220 $ hg tag -f foo
1212 1221 [a, foo, tip]
1213 1222
1214 1223 post-init hooks must not crash (issue4983)
1215 1224 This also creates the `to` repo for the next test block.
1216 1225
1217 1226 $ cd ..
1218 1227 $ cat << EOF >> hgrc-with-post-init-hook
1219 1228 > [hooks]
1220 1229 > post-init = sh -c "printenv.py --line post-init"
1221 1230 > EOF
1222 1231 $ HGRCPATH=hgrc-with-post-init-hook hg init to
1223 1232 post-init hook: HG_ARGS=init to
1224 1233 HG_HOOKNAME=post-init
1225 1234 HG_HOOKTYPE=post-init
1226 1235 HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''}
1227 1236 HG_PATS=['to']
1228 1237 HG_RESULT=0
1229 1238
1230 1239
1231 1240 new commits must be visible in pretxnchangegroup (issue3428)
1232 1241
1233 1242 $ echo '[hooks]' >> to/.hg/hgrc
1234 1243 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
1235 1244 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
1236 1245 $ echo a >> to/a
1237 1246 $ hg --cwd to ci -Ama
1238 1247 adding a
1239 1248 $ hg clone to from
1240 1249 updating to branch default
1241 1250 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1242 1251 $ echo aa >> from/a
1243 1252 $ hg --cwd from ci -mb
1244 1253 $ hg --cwd from push
1245 1254 pushing to $TESTTMP/to
1246 1255 searching for changes
1247 1256 changeset: 0:cb9a9f314b8b
1248 1257 tag: tip
1249 1258 user: test
1250 1259 date: Thu Jan 01 00:00:00 1970 +0000
1251 1260 summary: a
1252 1261
1253 1262 adding changesets
1254 1263 adding manifests
1255 1264 adding file changes
1256 1265 changeset: 1:9836a07b9b9d
1257 1266 tag: tip
1258 1267 user: test
1259 1268 date: Thu Jan 01 00:00:00 1970 +0000
1260 1269 summary: b
1261 1270
1262 1271 added 1 changesets with 1 changes to 1 files
1263 1272
1264 1273 pretxnclose hook failure should abort the transaction
1265 1274
1266 1275 $ hg init txnfailure
1267 1276 $ cd txnfailure
1268 1277 $ touch a && hg commit -Aqm a
1269 1278 $ cat >> .hg/hgrc <<EOF
1270 1279 > [hooks]
1271 1280 > pretxnclose.error = exit 1
1272 1281 > EOF
1273 1282 $ hg strip -r 0 --config extensions.strip=
1274 1283 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1275 1284 saved backup bundle to * (glob)
1276 1285 transaction abort!
1277 1286 rollback completed
1278 1287 strip failed, backup bundle stored in * (glob)
1279 1288 abort: pretxnclose.error hook exited with status 1
1280 1289 [40]
1281 1290 $ hg recover
1282 1291 no interrupted transaction available
1283 1292 [1]
1284 1293 $ cd ..
1285 1294
1286 1295 check whether HG_PENDING makes pending changes only in related
1287 1296 repositories visible to an external hook.
1288 1297
1289 1298 (emulate a transaction running concurrently by copied
1290 1299 .hg/store/00changelog.i.a in subsequent test)
1291 1300
1292 1301 $ cat > $TESTTMP/savepending.sh <<EOF
1293 1302 > cp .hg/store/00changelog.i.a .hg/store/00changelog.i.a.saved
1294 1303 > exit 1 # to avoid adding new revision for subsequent tests
1295 1304 > EOF
1296 1305 $ cd a
1297 1306 $ hg tip -q
1298 1307 4:539e4b31b6dc
1299 1308 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" commit -m "invisible"
1300 1309 transaction abort!
1301 1310 rollback completed
1302 1311 abort: pretxnclose hook exited with status 1
1303 1312 [40]
1304 1313 $ cp .hg/store/00changelog.i.a.saved .hg/store/00changelog.i.a
1305 1314
1306 1315 (check (in)visibility of new changeset while transaction running in
1307 1316 repo)
1308 1317
1309 1318 $ cat > $TESTTMP/checkpending.sh <<EOF
1310 1319 > echo '@a'
1311 1320 > hg -R "$TESTTMP/a" tip -q
1312 1321 > echo '@a/nested'
1313 1322 > hg -R "$TESTTMP/a/nested" tip -q
1314 1323 > exit 1 # to avoid adding new revision for subsequent tests
1315 1324 > EOF
1316 1325 $ hg init nested
1317 1326 $ cd nested
1318 1327 $ echo a > a
1319 1328 $ hg add a
1320 1329 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" commit -m '#0'
1321 1330 @a
1322 1331 4:539e4b31b6dc
1323 1332 @a/nested
1324 1333 0:bf5e395ced2c
1325 1334 transaction abort!
1326 1335 rollback completed
1327 1336 abort: pretxnclose hook exited with status 1
1328 1337 [40]
1329 1338
1330 1339 Hook from untrusted hgrc are reported as failure
1331 1340 ================================================
1332 1341
1333 1342 $ cat << EOF > $TESTTMP/untrusted.py
1334 1343 > from mercurial import scmutil, util
1335 1344 > def uisetup(ui):
1336 1345 > class untrustedui(ui.__class__):
1337 1346 > def _trusted(self, fp, f):
1338 1347 > if util.normpath(fp.name).endswith(b'untrusted/.hg/hgrc'):
1339 1348 > return False
1340 1349 > return super(untrustedui, self)._trusted(fp, f)
1341 1350 > ui.__class__ = untrustedui
1342 1351 > EOF
1343 1352 $ cat << EOF >> $HGRCPATH
1344 1353 > [extensions]
1345 1354 > untrusted=$TESTTMP/untrusted.py
1346 1355 > EOF
1347 1356 $ hg init untrusted
1348 1357 $ cd untrusted
1349 1358
1350 1359 Non-blocking hook
1351 1360 -----------------
1352 1361
1353 1362 $ cat << EOF >> .hg/hgrc
1354 1363 > [hooks]
1355 1364 > txnclose.testing=echo txnclose hook called
1356 1365 > EOF
1357 1366 $ touch a && hg commit -Aqm a
1358 1367 warning: untrusted hook txnclose.testing not executed
1359 1368 $ hg log
1360 1369 changeset: 0:3903775176ed
1361 1370 tag: tip
1362 1371 user: test
1363 1372 date: Thu Jan 01 00:00:00 1970 +0000
1364 1373 summary: a
1365 1374
1366 1375
1367 1376 Non-blocking hook
1368 1377 -----------------
1369 1378
1370 1379 $ cat << EOF >> .hg/hgrc
1371 1380 > [hooks]
1372 1381 > pretxnclose.testing=echo pre-txnclose hook called
1373 1382 > EOF
1374 1383 $ touch b && hg commit -Aqm a
1375 1384 transaction abort!
1376 1385 rollback completed
1377 1386 abort: untrusted hook pretxnclose.testing not executed
1378 1387 (see 'hg help config.trusted')
1379 1388 [40]
1380 1389 $ hg log
1381 1390 changeset: 0:3903775176ed
1382 1391 tag: tip
1383 1392 user: test
1384 1393 date: Thu Jan 01 00:00:00 1970 +0000
1385 1394 summary: a
1386 1395
1387 1396
1388 1397 unsetup the test
1389 1398 ----------------
1390 1399
1391 1400 # touch the file to unconfuse chg with a diffrent mtime
1392 1401 $ sleep 1
1393 1402 $ touch $TESTTMP/untrusted.py
1394 1403 $ cat << EOF >> $HGRCPATH
1395 1404 > [extensions]
1396 1405 > untrusted=!
1397 1406 > EOF
1398 1407
1399 1408 HGPLAIN setting in hooks
1400 1409 ========================
1401 1410
1402 1411 $ cat << EOF >> .hg/hgrc
1403 1412 > [hooks]
1404 1413 > pre-version.testing-default=sh -c "echo '### default ###' plain: \${HGPLAIN:-'<unset>'}"
1405 1414 > pre-version.testing-yes=sh -c "echo '### yes #######' plain: \${HGPLAIN:-'<unset>'}"
1406 1415 > pre-version.testing-yes:run-with-plain=yes
1407 1416 > pre-version.testing-no=sh -c "echo '### no ########' plain: \${HGPLAIN:-'<unset>'}"
1408 1417 > pre-version.testing-no:run-with-plain=no
1409 1418 > pre-version.testing-auto=sh -c "echo '### auto ######' plain: \${HGPLAIN:-'<unset>'}"
1410 1419 > pre-version.testing-auto:run-with-plain=auto
1411 1420 > EOF
1412 1421
1413 1422 $ (unset HGPLAIN; hg version --quiet)
1414 1423 ### default ### plain: 1
1415 1424 ### yes ####### plain: 1
1416 1425 ### no ######## plain: <unset>
1417 1426 ### auto ###### plain: <unset>
1418 1427 Mercurial Distributed SCM (*) (glob)
1419 1428
1420 1429 $ HGPLAIN=1 hg version --quiet
1421 1430 ### default ### plain: 1
1422 1431 ### yes ####### plain: 1
1423 1432 ### no ######## plain: <unset>
1424 1433 ### auto ###### plain: 1
1425 1434 Mercurial Distributed SCM (*) (glob)
General Comments 0
You need to be logged in to leave comments. Login now