mercurial/transaction.py
changeset 43076 2372284d9457
parent 43050 a614f26d4897
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    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
    71                     raise
    82                     raise
    72 
    83 
    73     backupfiles = []
    84     backupfiles = []
    74     for l, f, b, c in backupentries:
    85     for l, f, b, c in backupentries:
    75         if l not in vfsmap and c:
    86         if l not in vfsmap and c:
    76             report("couldn't handle %s: unknown cache location %s\n"
    87             report("couldn't handle %s: unknown cache location %s\n" % (b, l))
    77                         % (b, l))
       
    78         vfs = vfsmap[l]
    88         vfs = vfsmap[l]
    79         try:
    89         try:
    80             if f and b:
    90             if f and b:
    81                 filepath = vfs.join(f)
    91                 filepath = vfs.join(f)
    82                 backuppath = vfs.join(b)
    92                 backuppath = vfs.join(b)
   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 
   195         # holds callbacks to call during abort
   217         # holds callbacks to call during abort
   196         self._abortcallback = {}
   218         self._abortcallback = {}
   197 
   219 
   198     def __repr__(self):
   220     def __repr__(self):
   199         name = r'/'.join(self._names)
   221         name = r'/'.join(self._names)
   200         return (r'<transaction name=%s, count=%d, usages=%d>' %
   222         return r'<transaction name=%s, count=%d, usages=%d>' % (
   201                 (name, self._count, self._usages))
   223             name,
       
   224             self._count,
       
   225             self._usages,
       
   226         )
   202 
   227 
   203     def __del__(self):
   228     def __del__(self):
   204         if self._journal:
   229         if self._journal:
   205             self._abort()
   230             self._abort()
   206 
   231 
   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 
   332             order, filenames, genfunc, location = entry
   356             order, filenames, genfunc, location = entry
   333 
   357 
   334             # for generation at closing, check if it's before or after finalize
   358             # for generation at closing, check if it's before or after finalize
   335             postfinalize = group == gengrouppostfinalize
   359             postfinalize = group == gengrouppostfinalize
   336             if (
   360             if (
   337                     group != gengroupall
   361                 group != gengroupall
   338                     and (id in postfinalizegenerators) != postfinalize
   362                 and (id in postfinalizegenerators) != postfinalize
   339             ):
   363             ):
   340                 continue
   364                 continue
   341 
   365 
   342             vfs = self._vfsmap[location]
   366             vfs = self._vfsmap[location]
   343             files = []
   367             files = []
   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
   629     for l in lines:
   669     for l in lines:
   630         try:
   670         try:
   631             f, o = l.split('\0')
   671             f, o = l.split('\0')
   632             entries.append((f, int(o), None))
   672             entries.append((f, int(o), None))
   633         except ValueError:
   673         except ValueError:
   634             report(
   674             report(_("couldn't read journal entry %r!\n") % pycompat.bytestr(l))
   635                 _("couldn't read journal entry %r!\n") % pycompat.bytestr(l))
       
   636 
   675 
   637     backupjournal = "%s.backupfiles" % file
   676     backupjournal = "%s.backupfiles" % file
   638     if opener.exists(backupjournal):
   677     if opener.exists(backupjournal):
   639         fp = opener.open(backupjournal)
   678         fp = opener.open(backupjournal)
   640         lines = fp.readlines()
   679         lines = fp.readlines()
   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     )