eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/transaction.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # transaction.py - simple journalling scheme for mercurial
       
     2 #
       
     3 # This transaction scheme is intended to gracefully handle program
       
     4 # errors and interruptions. More serious failures like system crashes
       
     5 # can be recovered with an fsck-like tool. As the whole repository is
       
     6 # effectively log-structured, this should amount to simply truncating
       
     7 # anything that isn't referenced in the changelog.
       
     8 #
       
     9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
       
    10 #
       
    11 # This software may be used and distributed according to the terms of the
       
    12 # GNU General Public License version 2 or any later version.
       
    13 
       
    14 from i18n import _
       
    15 import os, errno
       
    16 import error
       
    17 
       
    18 def active(func):
       
    19     def _active(self, *args, **kwds):
       
    20         if self.count == 0:
       
    21             raise error.Abort(_(
       
    22                 'cannot use transaction when it is already committed/aborted'))
       
    23         return func(self, *args, **kwds)
       
    24     return _active
       
    25 
       
    26 def _playback(journal, report, opener, entries, unlink=True):
       
    27     for f, o, ignore in entries:
       
    28         if o or not unlink:
       
    29             try:
       
    30                 opener(f, 'a').truncate(o)
       
    31             except IOError:
       
    32                 report(_("failed to truncate %s\n") % f)
       
    33                 raise
       
    34         else:
       
    35             try:
       
    36                 fn = opener(f).name
       
    37                 os.unlink(fn)
       
    38             except (IOError, OSError), inst:
       
    39                 if inst.errno != errno.ENOENT:
       
    40                     raise
       
    41     os.unlink(journal)
       
    42 
       
    43 class transaction(object):
       
    44     def __init__(self, report, opener, journal, after=None, createmode=None):
       
    45         self.count = 1
       
    46         self.usages = 1
       
    47         self.report = report
       
    48         self.opener = opener
       
    49         self.after = after
       
    50         self.entries = []
       
    51         self.map = {}
       
    52         self.journal = journal
       
    53         self._queue = []
       
    54 
       
    55         self.file = open(self.journal, "w")
       
    56         if createmode is not None:
       
    57             os.chmod(self.journal, createmode & 0666)
       
    58 
       
    59     def __del__(self):
       
    60         if self.journal:
       
    61             self._abort()
       
    62 
       
    63     @active
       
    64     def startgroup(self):
       
    65         self._queue.append([])
       
    66 
       
    67     @active
       
    68     def endgroup(self):
       
    69         q = self._queue.pop()
       
    70         d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q])
       
    71         self.entries.extend(q)
       
    72         self.file.write(d)
       
    73         self.file.flush()
       
    74 
       
    75     @active
       
    76     def add(self, file, offset, data=None):
       
    77         if file in self.map:
       
    78             return
       
    79         if self._queue:
       
    80             self._queue[-1].append((file, offset, data))
       
    81             return
       
    82 
       
    83         self.entries.append((file, offset, data))
       
    84         self.map[file] = len(self.entries) - 1
       
    85         # add enough data to the journal to do the truncate
       
    86         self.file.write("%s\0%d\n" % (file, offset))
       
    87         self.file.flush()
       
    88 
       
    89     @active
       
    90     def find(self, file):
       
    91         if file in self.map:
       
    92             return self.entries[self.map[file]]
       
    93         return None
       
    94 
       
    95     @active
       
    96     def replace(self, file, offset, data=None):
       
    97         '''
       
    98         replace can only replace already committed entries
       
    99         that are not pending in the queue
       
   100         '''
       
   101 
       
   102         if file not in self.map:
       
   103             raise KeyError(file)
       
   104         index = self.map[file]
       
   105         self.entries[index] = (file, offset, data)
       
   106         self.file.write("%s\0%d\n" % (file, offset))
       
   107         self.file.flush()
       
   108 
       
   109     @active
       
   110     def nest(self):
       
   111         self.count += 1
       
   112         self.usages += 1
       
   113         return self
       
   114 
       
   115     def release(self):
       
   116         if self.count > 0:
       
   117             self.usages -= 1
       
   118         # if the transaction scopes are left without being closed, fail
       
   119         if self.count > 0 and self.usages == 0:
       
   120             self._abort()
       
   121 
       
   122     def running(self):
       
   123         return self.count > 0
       
   124 
       
   125     @active
       
   126     def close(self):
       
   127         '''commit the transaction'''
       
   128         self.count -= 1
       
   129         if self.count != 0:
       
   130             return
       
   131         self.file.close()
       
   132         self.entries = []
       
   133         if self.after:
       
   134             self.after()
       
   135         if os.path.isfile(self.journal):
       
   136             os.unlink(self.journal)
       
   137         self.journal = None
       
   138 
       
   139     @active
       
   140     def abort(self):
       
   141         '''abort the transaction (generally called on error, or when the
       
   142         transaction is not explicitly committed before going out of
       
   143         scope)'''
       
   144         self._abort()
       
   145 
       
   146     def _abort(self):
       
   147         self.count = 0
       
   148         self.usages = 0
       
   149         self.file.close()
       
   150 
       
   151         try:
       
   152             if not self.entries:
       
   153                 if self.journal:
       
   154                     os.unlink(self.journal)
       
   155                 return
       
   156 
       
   157             self.report(_("transaction abort!\n"))
       
   158 
       
   159             try:
       
   160                 _playback(self.journal, self.report, self.opener,
       
   161                           self.entries, False)
       
   162                 self.report(_("rollback completed\n"))
       
   163             except:
       
   164                 self.report(_("rollback failed - please run hg recover\n"))
       
   165         finally:
       
   166             self.journal = None
       
   167 
       
   168 
       
   169 def rollback(opener, file, report):
       
   170     entries = []
       
   171 
       
   172     for l in open(file).readlines():
       
   173         f, o = l.split('\0')
       
   174         entries.append((f, int(o), None))
       
   175 
       
   176     _playback(file, report, opener, entries)