19 from . import ( |
19 from . import ( |
20 error, |
20 error, |
21 pycompat, |
21 pycompat, |
22 util, |
22 util, |
23 ) |
23 ) |
24 from .utils import ( |
24 from .utils import stringutil |
25 stringutil, |
|
26 ) |
|
27 |
25 |
28 version = 2 |
26 version = 2 |
29 |
27 |
30 # These are the file generators that should only be executed after the |
28 # These are the file generators that should only be executed after the |
31 # finalizers are done, since they rely on the output of the finalizers (like |
29 # finalizers are done, since they rely on the output of the finalizers (like |
32 # the changelog having been written). |
30 # the changelog having been written). |
33 postfinalizegenerators = { |
31 postfinalizegenerators = {'bookmarks', 'dirstate'} |
34 'bookmarks', |
32 |
35 'dirstate' |
33 gengroupall = 'all' |
36 } |
34 gengroupprefinalize = 'prefinalize' |
37 |
35 gengrouppostfinalize = 'postfinalize' |
38 gengroupall='all' |
36 |
39 gengroupprefinalize='prefinalize' |
|
40 gengrouppostfinalize='postfinalize' |
|
41 |
37 |
42 def active(func): |
38 def active(func): |
43 def _active(self, *args, **kwds): |
39 def _active(self, *args, **kwds): |
44 if self._count == 0: |
40 if self._count == 0: |
45 raise error.Abort(_( |
41 raise error.Abort( |
46 'cannot use transaction when it is already committed/aborted')) |
42 _('cannot use transaction when it is already committed/aborted') |
|
43 ) |
47 return func(self, *args, **kwds) |
44 return func(self, *args, **kwds) |
|
45 |
48 return _active |
46 return _active |
49 |
47 |
50 def _playback(journal, report, opener, vfsmap, entries, backupentries, |
48 |
51 unlink=True, checkambigfiles=None): |
49 def _playback( |
|
50 journal, |
|
51 report, |
|
52 opener, |
|
53 vfsmap, |
|
54 entries, |
|
55 backupentries, |
|
56 unlink=True, |
|
57 checkambigfiles=None, |
|
58 ): |
52 for f, o, _ignore in entries: |
59 for f, o, _ignore in entries: |
53 if o or not unlink: |
60 if o or not unlink: |
54 checkambig = checkambigfiles and (f, '') in checkambigfiles |
61 checkambig = checkambigfiles and (f, '') in checkambigfiles |
55 try: |
62 try: |
56 fp = opener(f, 'a', checkambig=checkambig) |
63 fp = opener(f, 'a', checkambig=checkambig) |
57 if fp.tell() < o: |
64 if fp.tell() < o: |
58 raise error.Abort(_( |
65 raise error.Abort( |
|
66 _( |
59 "attempted to truncate %s to %d bytes, but it was " |
67 "attempted to truncate %s to %d bytes, but it was " |
60 "already %d bytes\n") % (f, o, fp.tell())) |
68 "already %d bytes\n" |
|
69 ) |
|
70 % (f, o, fp.tell()) |
|
71 ) |
61 fp.truncate(o) |
72 fp.truncate(o) |
62 fp.close() |
73 fp.close() |
63 except IOError: |
74 except IOError: |
64 report(_("failed to truncate %s\n") % f) |
75 report(_("failed to truncate %s\n") % f) |
65 raise |
76 raise |
107 opener.unlink(f) |
117 opener.unlink(f) |
108 except (IOError, OSError, error.Abort): |
118 except (IOError, OSError, error.Abort): |
109 # only pure backup file remains, it is sage to ignore any error |
119 # only pure backup file remains, it is sage to ignore any error |
110 pass |
120 pass |
111 |
121 |
|
122 |
112 class transaction(util.transactional): |
123 class transaction(util.transactional): |
113 def __init__(self, report, opener, vfsmap, journalname, undoname=None, |
124 def __init__( |
114 after=None, createmode=None, validator=None, releasefn=None, |
125 self, |
115 checkambigfiles=None, name=r'<unnamed>'): |
126 report, |
|
127 opener, |
|
128 vfsmap, |
|
129 journalname, |
|
130 undoname=None, |
|
131 after=None, |
|
132 createmode=None, |
|
133 validator=None, |
|
134 releasefn=None, |
|
135 checkambigfiles=None, |
|
136 name=r'<unnamed>', |
|
137 ): |
116 """Begin a new transaction |
138 """Begin a new transaction |
117 |
139 |
118 Begins a new transaction that allows rolling back writes in the event of |
140 Begins a new transaction that allows rolling back writes in the event of |
119 an exception. |
141 an exception. |
120 |
142 |
288 failure and success). |
313 failure and success). |
289 """ |
314 """ |
290 self._addbackupentry((location, '', tmpfile, False)) |
315 self._addbackupentry((location, '', tmpfile, False)) |
291 |
316 |
292 @active |
317 @active |
293 def addfilegenerator(self, genid, filenames, genfunc, order=0, |
318 def addfilegenerator(self, genid, filenames, genfunc, order=0, location=''): |
294 location=''): |
|
295 """add a function to generates some files at transaction commit |
319 """add a function to generates some files at transaction commit |
296 |
320 |
297 The `genfunc` argument is a function capable of generating proper |
321 The `genfunc` argument is a function capable of generating proper |
298 content of each entry in the `filename` tuple. |
322 content of each entry in the `filename` tuple. |
299 |
323 |
348 self.registertmp(name, location=location) |
372 self.registertmp(name, location=location) |
349 checkambig = False |
373 checkambig = False |
350 else: |
374 else: |
351 self.addbackup(name, location=location) |
375 self.addbackup(name, location=location) |
352 checkambig = (name, location) in self._checkambigfiles |
376 checkambig = (name, location) in self._checkambigfiles |
353 files.append(vfs(name, 'w', atomictemp=True, |
377 files.append( |
354 checkambig=checkambig)) |
378 vfs(name, 'w', atomictemp=True, checkambig=checkambig) |
|
379 ) |
355 genfunc(*files) |
380 genfunc(*files) |
356 for f in files: |
381 for f in files: |
357 f.close() |
382 f.close() |
358 # skip discard() loop since we're sure no open file remains |
383 # skip discard() loop since we're sure no open file remains |
359 del files[:] |
384 del files[:] |
467 @active |
492 @active |
468 def close(self): |
493 def close(self): |
469 '''commit the transaction''' |
494 '''commit the transaction''' |
470 if self._count == 1: |
495 if self._count == 1: |
471 self._validator(self) # will raise exception if needed |
496 self._validator(self) # will raise exception if needed |
472 self._validator = None # Help prevent cycles. |
497 self._validator = None # Help prevent cycles. |
473 self._generatefiles(group=gengroupprefinalize) |
498 self._generatefiles(group=gengroupprefinalize) |
474 categories = sorted(self._finalizecallback) |
499 categories = sorted(self._finalizecallback) |
475 for cat in categories: |
500 for cat in categories: |
476 self._finalizecallback[cat](self) |
501 self._finalizecallback[cat](self) |
477 # Prevent double usage and help clear cycles. |
502 # Prevent double usage and help clear cycles. |
484 self._file.close() |
509 self._file.close() |
485 self._backupsfile.close() |
510 self._backupsfile.close() |
486 # cleanup temporary files |
511 # cleanup temporary files |
487 for l, f, b, c in self._backupentries: |
512 for l, f, b, c in self._backupentries: |
488 if l not in self._vfsmap and c: |
513 if l not in self._vfsmap and c: |
489 self._report("couldn't remove %s: unknown cache location %s\n" |
514 self._report( |
490 % (b, l)) |
515 "couldn't remove %s: unknown cache location %s\n" % (b, l) |
|
516 ) |
491 continue |
517 continue |
492 vfs = self._vfsmap[l] |
518 vfs = self._vfsmap[l] |
493 if not f and b and vfs.exists(b): |
519 if not f and b and vfs.exists(b): |
494 try: |
520 try: |
495 vfs.unlink(b) |
521 vfs.unlink(b) |
496 except (IOError, OSError, error.Abort) as inst: |
522 except (IOError, OSError, error.Abort) as inst: |
497 if not c: |
523 if not c: |
498 raise |
524 raise |
499 # Abort may be raise by read only opener |
525 # Abort may be raise by read only opener |
500 self._report("couldn't remove %s: %s\n" |
526 self._report( |
501 % (vfs.join(b), inst)) |
527 "couldn't remove %s: %s\n" % (vfs.join(b), inst) |
|
528 ) |
502 self._entries = [] |
529 self._entries = [] |
503 self._writeundo() |
530 self._writeundo() |
504 if self._after: |
531 if self._after: |
505 self._after() |
532 self._after() |
506 self._after = None # Help prevent cycles. |
533 self._after = None # Help prevent cycles. |
507 if self._opener.isfile(self._backupjournal): |
534 if self._opener.isfile(self._backupjournal): |
508 self._opener.unlink(self._backupjournal) |
535 self._opener.unlink(self._backupjournal) |
509 if self._opener.isfile(self._journal): |
536 if self._opener.isfile(self._journal): |
510 self._opener.unlink(self._journal) |
537 self._opener.unlink(self._journal) |
511 for l, _f, b, c in self._backupentries: |
538 for l, _f, b, c in self._backupentries: |
512 if l not in self._vfsmap and c: |
539 if l not in self._vfsmap and c: |
513 self._report("couldn't remove %s: unknown cache location" |
540 self._report( |
514 "%s\n" % (b, l)) |
541 "couldn't remove %s: unknown cache location" "%s\n" % (b, l) |
|
542 ) |
515 continue |
543 continue |
516 vfs = self._vfsmap[l] |
544 vfs = self._vfsmap[l] |
517 if b and vfs.exists(b): |
545 if b and vfs.exists(b): |
518 try: |
546 try: |
519 vfs.unlink(b) |
547 vfs.unlink(b) |
520 except (IOError, OSError, error.Abort) as inst: |
548 except (IOError, OSError, error.Abort) as inst: |
521 if not c: |
549 if not c: |
522 raise |
550 raise |
523 # Abort may be raise by read only opener |
551 # Abort may be raise by read only opener |
524 self._report("couldn't remove %s: %s\n" |
552 self._report( |
525 % (vfs.join(b), inst)) |
553 "couldn't remove %s: %s\n" % (vfs.join(b), inst) |
|
554 ) |
526 self._backupentries = [] |
555 self._backupentries = [] |
527 self._journal = None |
556 self._journal = None |
528 |
557 |
529 self._releasefn(self, True) # notify success of closing transaction |
558 self._releasefn(self, True) # notify success of closing transaction |
530 self._releasefn = None # Help prevent cycles. |
559 self._releasefn = None # Help prevent cycles. |
531 |
560 |
532 # run post close action |
561 # run post close action |
533 categories = sorted(self._postclosecallback) |
562 categories = sorted(self._postclosecallback) |
534 for cat in categories: |
563 for cat in categories: |
535 self._postclosecallback[cat](self) |
564 self._postclosecallback[cat](self) |
545 |
574 |
546 def _writeundo(self): |
575 def _writeundo(self): |
547 """write transaction data for possible future undo call""" |
576 """write transaction data for possible future undo call""" |
548 if self._undoname is None: |
577 if self._undoname is None: |
549 return |
578 return |
550 undobackupfile = self._opener.open("%s.backupfiles" % self._undoname, |
579 undobackupfile = self._opener.open( |
551 'w') |
580 "%s.backupfiles" % self._undoname, 'w' |
|
581 ) |
552 undobackupfile.write('%d\n' % version) |
582 undobackupfile.write('%d\n' % version) |
553 for l, f, b, c in self._backupentries: |
583 for l, f, b, c in self._backupentries: |
554 if not f: # temporary file |
584 if not f: # temporary file |
555 continue |
585 continue |
556 if not b: |
586 if not b: |
557 u = '' |
587 u = '' |
558 else: |
588 else: |
559 if l not in self._vfsmap and c: |
589 if l not in self._vfsmap and c: |
560 self._report("couldn't remove %s: unknown cache location" |
590 self._report( |
561 "%s\n" % (b, l)) |
591 "couldn't remove %s: unknown cache location" |
|
592 "%s\n" % (b, l) |
|
593 ) |
562 continue |
594 continue |
563 vfs = self._vfsmap[l] |
595 vfs = self._vfsmap[l] |
564 base, name = vfs.split(b) |
596 base, name = vfs.split(b) |
565 assert name.startswith(self._journal), name |
597 assert name.startswith(self._journal), name |
566 uname = name.replace(self._journal, self._undoname, 1) |
598 uname = name.replace(self._journal, self._undoname, 1) |
567 u = vfs.reljoin(base, uname) |
599 u = vfs.reljoin(base, uname) |
568 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True) |
600 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True) |
569 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c)) |
601 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c)) |
570 undobackupfile.close() |
602 undobackupfile.close() |
571 |
603 |
572 |
|
573 def _abort(self): |
604 def _abort(self): |
574 self._count = 0 |
605 self._count = 0 |
575 self._usages = 0 |
606 self._usages = 0 |
576 self._file.close() |
607 self._file.close() |
577 self._backupsfile.close() |
608 self._backupsfile.close() |
589 try: |
620 try: |
590 for cat in sorted(self._abortcallback): |
621 for cat in sorted(self._abortcallback): |
591 self._abortcallback[cat](self) |
622 self._abortcallback[cat](self) |
592 # Prevent double usage and help clear cycles. |
623 # Prevent double usage and help clear cycles. |
593 self._abortcallback = None |
624 self._abortcallback = None |
594 _playback(self._journal, self._report, self._opener, |
625 _playback( |
595 self._vfsmap, self._entries, self._backupentries, |
626 self._journal, |
596 False, checkambigfiles=self._checkambigfiles) |
627 self._report, |
|
628 self._opener, |
|
629 self._vfsmap, |
|
630 self._entries, |
|
631 self._backupentries, |
|
632 False, |
|
633 checkambigfiles=self._checkambigfiles, |
|
634 ) |
597 self._report(_("rollback completed\n")) |
635 self._report(_("rollback completed\n")) |
598 except BaseException as exc: |
636 except BaseException as exc: |
599 self._report(_("rollback failed - please run hg recover\n")) |
637 self._report(_("rollback failed - please run hg recover\n")) |
600 self._report(_("(failure reason: %s)\n") |
638 self._report( |
601 % stringutil.forcebytestr(exc)) |
639 _("(failure reason: %s)\n") % stringutil.forcebytestr(exc) |
|
640 ) |
602 finally: |
641 finally: |
603 self._journal = None |
642 self._journal = None |
604 self._releasefn(self, False) # notify failure of transaction |
643 self._releasefn(self, False) # notify failure of transaction |
605 self._releasefn = None # Help prevent cycles. |
644 self._releasefn = None # Help prevent cycles. |
|
645 |
606 |
646 |
607 def rollback(opener, vfsmap, file, report, checkambigfiles=None): |
647 def rollback(opener, vfsmap, file, report, checkambigfiles=None): |
608 """Rolls back the transaction contained in the given file |
648 """Rolls back the transaction contained in the given file |
609 |
649 |
610 Reads the entries in the specified file, and the corresponding |
650 Reads the entries in the specified file, and the corresponding |
646 # Shave off the trailing newline |
685 # Shave off the trailing newline |
647 line = line[:-1] |
686 line = line[:-1] |
648 l, f, b, c = line.split('\0') |
687 l, f, b, c = line.split('\0') |
649 backupentries.append((l, f, b, bool(c))) |
688 backupentries.append((l, f, b, bool(c))) |
650 else: |
689 else: |
651 report(_("journal was created by a different version of " |
690 report( |
652 "Mercurial\n")) |
691 _( |
653 |
692 "journal was created by a different version of " |
654 _playback(file, report, opener, vfsmap, entries, backupentries, |
693 "Mercurial\n" |
655 checkambigfiles=checkambigfiles) |
694 ) |
|
695 ) |
|
696 |
|
697 _playback( |
|
698 file, |
|
699 report, |
|
700 opener, |
|
701 vfsmap, |
|
702 entries, |
|
703 backupentries, |
|
704 checkambigfiles=checkambigfiles, |
|
705 ) |