##// END OF EJS Templates
transaction: more specific exceptions, os.unlink can raise OSError
Benoit Boissinot -
r9686:ddf2adf8 default
parent child Browse files
Show More
@@ -1,165 +1,165
1 1 # transaction.py - simple journalling 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 Matt Mackall <mpm@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, incorporated herein by reference.
13 13
14 14 from i18n import _
15 15 import os, errno
16 16 import error
17 17
18 18 def active(func):
19 19 def _active(self, *args, **kwds):
20 20 if self.count == 0:
21 21 raise error.Abort(_(
22 22 'cannot use transaction when it is already committed/aborted'))
23 23 return func(self, *args, **kwds)
24 24 return _active
25 25
26 26 def _playback(journal, report, opener, entries, unlink=True):
27 27 for f, o, ignore in entries:
28 28 if o or not unlink:
29 29 try:
30 30 opener(f, 'a').truncate(o)
31 except:
31 except IOError:
32 32 report(_("failed to truncate %s\n") % f)
33 33 raise
34 34 else:
35 35 try:
36 36 fn = opener(f).name
37 37 os.unlink(fn)
38 except IOError, inst:
38 except (IOError, OSError), inst:
39 39 if inst.errno != errno.ENOENT:
40 40 raise
41 41 os.unlink(journal)
42 42
43 43 class transaction(object):
44 44 def __init__(self, report, opener, journal, after=None, createmode=None):
45 45 self.journal = None
46 46
47 47 self.count = 1
48 48 self.report = report
49 49 self.opener = opener
50 50 self.after = after
51 51 self.entries = []
52 52 self.map = {}
53 53 self.journal = journal
54 54 self._queue = []
55 55
56 56 self.file = open(self.journal, "w")
57 57 if createmode is not None:
58 58 os.chmod(self.journal, createmode & 0666)
59 59
60 60 def __del__(self):
61 61 if self.journal:
62 62 if self.entries: self._abort()
63 63 self.file.close()
64 64
65 65 @active
66 66 def startgroup(self):
67 67 self._queue.append([])
68 68
69 69 @active
70 70 def endgroup(self):
71 71 q = self._queue.pop()
72 72 d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q])
73 73 self.entries.extend(q)
74 74 self.file.write(d)
75 75 self.file.flush()
76 76
77 77 @active
78 78 def add(self, file, offset, data=None):
79 79 if file in self.map: return
80 80
81 81 if self._queue:
82 82 self._queue[-1].append((file, offset, data))
83 83 return
84 84
85 85 self.entries.append((file, offset, data))
86 86 self.map[file] = len(self.entries) - 1
87 87 # add enough data to the journal to do the truncate
88 88 self.file.write("%s\0%d\n" % (file, offset))
89 89 self.file.flush()
90 90
91 91 @active
92 92 def find(self, file):
93 93 if file in self.map:
94 94 return self.entries[self.map[file]]
95 95 return None
96 96
97 97 @active
98 98 def replace(self, file, offset, data=None):
99 99 '''
100 100 replace can only replace already committed entries
101 101 that are not pending in the queue
102 102 '''
103 103
104 104 if file not in self.map:
105 105 raise KeyError(file)
106 106 index = self.map[file]
107 107 self.entries[index] = (file, offset, data)
108 108 self.file.write("%s\0%d\n" % (file, offset))
109 109 self.file.flush()
110 110
111 111 @active
112 112 def nest(self):
113 113 self.count += 1
114 114 return self
115 115
116 116 def running(self):
117 117 return self.count > 0
118 118
119 119 @active
120 120 def close(self):
121 121 '''commit the transaction'''
122 122 self.count -= 1
123 123 if self.count != 0:
124 124 return
125 125 self.file.close()
126 126 self.entries = []
127 127 if self.after:
128 128 self.after()
129 129 else:
130 130 os.unlink(self.journal)
131 131 self.journal = None
132 132
133 133 @active
134 134 def abort(self):
135 135 '''abort the transaction (generally called on error, or when the
136 136 transaction is not explicitly committed before going out of
137 137 scope)'''
138 138 self._abort()
139 139
140 140 def _abort(self):
141 141 self.count = 0
142 142 self.file.close()
143 143
144 144 if not self.entries: return
145 145
146 146 self.report(_("transaction abort!\n"))
147 147
148 148 try:
149 149 try:
150 150 _playback(self.journal, self.report, self.opener, self.entries, False)
151 151 self.report(_("rollback completed\n"))
152 152 except:
153 153 self.report(_("rollback failed - please run hg recover\n"))
154 154 finally:
155 155 self.journal = None
156 156
157 157
158 158 def rollback(opener, file, report):
159 159 entries = []
160 160
161 161 for l in open(file).readlines():
162 162 f, o = l.split('\0')
163 163 entries.append((f, int(o), None))
164 164
165 165 _playback(file, report, opener, entries)
General Comments 0
You need to be logged in to leave comments. Login now