mercurial/transaction.py
changeset 39676 77c4e2ae9f07
parent 39675 da9ce63bfa9b
child 39677 d27fde3e023e
equal deleted inserted replaced
39675:da9ce63bfa9b 39676:77c4e2ae9f07
   129         vfsmap[''] = opener  # set default value
   129         vfsmap[''] = opener  # set default value
   130         self._vfsmap = vfsmap
   130         self._vfsmap = vfsmap
   131         self.after = after
   131         self.after = after
   132         self.entries = []
   132         self.entries = []
   133         self.map = {}
   133         self.map = {}
   134         self.journal = journalname
   134         self._journal = journalname
   135         self._undoname = undoname
   135         self._undoname = undoname
   136         self._queue = []
   136         self._queue = []
   137         # A callback to validate transaction content before closing it.
   137         # A callback to validate transaction content before closing it.
   138         # should raise exception is anything is wrong.
   138         # should raise exception is anything is wrong.
   139         # target user is repository hooks.
   139         # target user is repository hooks.
   155         # transaction.
   155         # transaction.
   156         self.changes = {}
   156         self.changes = {}
   157 
   157 
   158         # a dict of arguments to be passed to hooks
   158         # a dict of arguments to be passed to hooks
   159         self.hookargs = {}
   159         self.hookargs = {}
   160         self.file = opener.open(self.journal, "w")
   160         self.file = opener.open(self._journal, "w")
   161 
   161 
   162         # a list of ('location', 'path', 'backuppath', cache) entries.
   162         # a list of ('location', 'path', 'backuppath', cache) entries.
   163         # - if 'backuppath' is empty, no file existed at backup time
   163         # - if 'backuppath' is empty, no file existed at backup time
   164         # - if 'path' is empty, this is a temporary transaction file
   164         # - if 'path' is empty, this is a temporary transaction file
   165         # - if 'location' is not empty, the path is outside main opener reach.
   165         # - if 'location' is not empty, the path is outside main opener reach.
   166         #   use 'location' value as a key in a vfsmap to find the right 'vfs'
   166         #   use 'location' value as a key in a vfsmap to find the right 'vfs'
   167         # (cache is currently unused)
   167         # (cache is currently unused)
   168         self._backupentries = []
   168         self._backupentries = []
   169         self._backupmap = {}
   169         self._backupmap = {}
   170         self._backupjournal = "%s.backupfiles" % self.journal
   170         self._backupjournal = "%s.backupfiles" % self._journal
   171         self._backupsfile = opener.open(self._backupjournal, 'w')
   171         self._backupsfile = opener.open(self._backupjournal, 'w')
   172         self._backupsfile.write('%d\n' % version)
   172         self._backupsfile.write('%d\n' % version)
   173 
   173 
   174         if createmode is not None:
   174         if createmode is not None:
   175             opener.chmod(self.journal, createmode & 0o666)
   175             opener.chmod(self._journal, createmode & 0o666)
   176             opener.chmod(self._backupjournal, createmode & 0o666)
   176             opener.chmod(self._backupjournal, createmode & 0o666)
   177 
   177 
   178         # hold file generations to be performed on commit
   178         # hold file generations to be performed on commit
   179         self._filegenerators = {}
   179         self._filegenerators = {}
   180         # hold callback to write pending data for hooks
   180         # hold callback to write pending data for hooks
   192         name = r'/'.join(self.names)
   192         name = r'/'.join(self.names)
   193         return (r'<transaction name=%s, count=%d, usages=%d>' %
   193         return (r'<transaction name=%s, count=%d, usages=%d>' %
   194                 (name, self._count, self._usages))
   194                 (name, self._count, self._usages))
   195 
   195 
   196     def __del__(self):
   196     def __del__(self):
   197         if self.journal:
   197         if self._journal:
   198             self._abort()
   198             self._abort()
   199 
   199 
   200     @active
   200     @active
   201     def startgroup(self):
   201     def startgroup(self):
   202         """delay registration of file entry
   202         """delay registration of file entry
   253 
   253 
   254         if file in self.map or file in self._backupmap:
   254         if file in self.map or file in self._backupmap:
   255             return
   255             return
   256         vfs = self._vfsmap[location]
   256         vfs = self._vfsmap[location]
   257         dirname, filename = vfs.split(file)
   257         dirname, filename = vfs.split(file)
   258         backupfilename = "%s.backup.%s" % (self.journal, filename)
   258         backupfilename = "%s.backup.%s" % (self._journal, filename)
   259         backupfile = vfs.reljoin(dirname, backupfilename)
   259         backupfile = vfs.reljoin(dirname, backupfilename)
   260         if vfs.exists(file):
   260         if vfs.exists(file):
   261             filepath = vfs.join(file)
   261             filepath = vfs.join(file)
   262             backuppath = vfs.join(backupfile)
   262             backuppath = vfs.join(backupfile)
   263             util.copyfile(filepath, backuppath, hardlink=hardlink)
   263             util.copyfile(filepath, backuppath, hardlink=hardlink)
   491         if self.after:
   491         if self.after:
   492             self.after()
   492             self.after()
   493             self.after = None # Help prevent cycles.
   493             self.after = None # Help prevent cycles.
   494         if self.opener.isfile(self._backupjournal):
   494         if self.opener.isfile(self._backupjournal):
   495             self.opener.unlink(self._backupjournal)
   495             self.opener.unlink(self._backupjournal)
   496         if self.opener.isfile(self.journal):
   496         if self.opener.isfile(self._journal):
   497             self.opener.unlink(self.journal)
   497             self.opener.unlink(self._journal)
   498         for l, _f, b, c in self._backupentries:
   498         for l, _f, b, c in self._backupentries:
   499             if l not in self._vfsmap and c:
   499             if l not in self._vfsmap and c:
   500                 self.report("couldn't remove %s: unknown cache location"
   500                 self.report("couldn't remove %s: unknown cache location"
   501                             "%s\n" % (b, l))
   501                             "%s\n" % (b, l))
   502                 continue
   502                 continue
   509                         raise
   509                         raise
   510                     # Abort may be raise by read only opener
   510                     # Abort may be raise by read only opener
   511                     self.report("couldn't remove %s: %s\n"
   511                     self.report("couldn't remove %s: %s\n"
   512                                 % (vfs.join(b), inst))
   512                                 % (vfs.join(b), inst))
   513         self._backupentries = []
   513         self._backupentries = []
   514         self.journal = None
   514         self._journal = None
   515 
   515 
   516         self.releasefn(self, True) # notify success of closing transaction
   516         self.releasefn(self, True) # notify success of closing transaction
   517         self.releasefn = None # Help prevent cycles.
   517         self.releasefn = None # Help prevent cycles.
   518 
   518 
   519         # run post close action
   519         # run post close action
   547                     self.report("couldn't remove %s: unknown cache location"
   547                     self.report("couldn't remove %s: unknown cache location"
   548                                 "%s\n" % (b, l))
   548                                 "%s\n" % (b, l))
   549                     continue
   549                     continue
   550                 vfs = self._vfsmap[l]
   550                 vfs = self._vfsmap[l]
   551                 base, name = vfs.split(b)
   551                 base, name = vfs.split(b)
   552                 assert name.startswith(self.journal), name
   552                 assert name.startswith(self._journal), name
   553                 uname = name.replace(self.journal, self._undoname, 1)
   553                 uname = name.replace(self._journal, self._undoname, 1)
   554                 u = vfs.reljoin(base, uname)
   554                 u = vfs.reljoin(base, uname)
   555                 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
   555                 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
   556             undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
   556             undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
   557         undobackupfile.close()
   557         undobackupfile.close()
   558 
   558 
   565 
   565 
   566         try:
   566         try:
   567             if not self.entries and not self._backupentries:
   567             if not self.entries and not self._backupentries:
   568                 if self._backupjournal:
   568                 if self._backupjournal:
   569                     self.opener.unlink(self._backupjournal)
   569                     self.opener.unlink(self._backupjournal)
   570                 if self.journal:
   570                 if self._journal:
   571                     self.opener.unlink(self.journal)
   571                     self.opener.unlink(self._journal)
   572                 return
   572                 return
   573 
   573 
   574             self.report(_("transaction abort!\n"))
   574             self.report(_("transaction abort!\n"))
   575 
   575 
   576             try:
   576             try:
   577                 for cat in sorted(self._abortcallback):
   577                 for cat in sorted(self._abortcallback):
   578                     self._abortcallback[cat](self)
   578                     self._abortcallback[cat](self)
   579                 # Prevent double usage and help clear cycles.
   579                 # Prevent double usage and help clear cycles.
   580                 self._abortcallback = None
   580                 self._abortcallback = None
   581                 _playback(self.journal, self.report, self.opener, self._vfsmap,
   581                 _playback(self._journal, self.report, self.opener, self._vfsmap,
   582                           self.entries, self._backupentries, False,
   582                           self.entries, self._backupentries, False,
   583                           checkambigfiles=self.checkambigfiles)
   583                           checkambigfiles=self.checkambigfiles)
   584                 self.report(_("rollback completed\n"))
   584                 self.report(_("rollback completed\n"))
   585             except BaseException:
   585             except BaseException:
   586                 self.report(_("rollback failed - please run hg recover\n"))
   586                 self.report(_("rollback failed - please run hg recover\n"))
   587         finally:
   587         finally:
   588             self.journal = None
   588             self._journal = None
   589             self.releasefn(self, False) # notify failure of transaction
   589             self.releasefn(self, False) # notify failure of transaction
   590             self.releasefn = None # Help prevent cycles.
   590             self.releasefn = None # Help prevent cycles.
   591 
   591 
   592 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
   592 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
   593     """Rolls back the transaction contained in the given file
   593     """Rolls back the transaction contained in the given file