##// END OF EJS Templates
spelling: journaling
timeless@mozdev.org -
r17498:894ceced default
parent child Browse files
Show More
@@ -1,183 +1,183 b''
1 # transaction.py - simple journalling scheme for mercurial
1 # transaction.py - simple journaling 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 or any later version.
13 13
14 14 from i18n import _
15 15 import os, errno
16 16 import error, util
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 fp = opener(f, 'a')
31 31 fp.truncate(o)
32 32 fp.close()
33 33 except IOError:
34 34 report(_("failed to truncate %s\n") % f)
35 35 raise
36 36 else:
37 37 try:
38 38 fp = opener(f)
39 39 fn = fp.name
40 40 fp.close()
41 41 util.unlink(fn)
42 42 except (IOError, OSError), inst:
43 43 if inst.errno != errno.ENOENT:
44 44 raise
45 45 util.unlink(journal)
46 46
47 47 class transaction(object):
48 48 def __init__(self, report, opener, journal, after=None, createmode=None):
49 49 self.count = 1
50 50 self.usages = 1
51 51 self.report = report
52 52 self.opener = opener
53 53 self.after = after
54 54 self.entries = []
55 55 self.map = {}
56 56 self.journal = journal
57 57 self._queue = []
58 58
59 59 self.file = util.posixfile(self.journal, "w")
60 60 if createmode is not None:
61 61 os.chmod(self.journal, createmode & 0666)
62 62
63 63 def __del__(self):
64 64 if self.journal:
65 65 self._abort()
66 66
67 67 @active
68 68 def startgroup(self):
69 69 self._queue.append([])
70 70
71 71 @active
72 72 def endgroup(self):
73 73 q = self._queue.pop()
74 74 d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q])
75 75 self.entries.extend(q)
76 76 self.file.write(d)
77 77 self.file.flush()
78 78
79 79 @active
80 80 def add(self, file, offset, data=None):
81 81 if file in self.map:
82 82 return
83 83 if self._queue:
84 84 self._queue[-1].append((file, offset, data))
85 85 return
86 86
87 87 self.entries.append((file, offset, data))
88 88 self.map[file] = len(self.entries) - 1
89 89 # add enough data to the journal to do the truncate
90 90 self.file.write("%s\0%d\n" % (file, offset))
91 91 self.file.flush()
92 92
93 93 @active
94 94 def find(self, file):
95 95 if file in self.map:
96 96 return self.entries[self.map[file]]
97 97 return None
98 98
99 99 @active
100 100 def replace(self, file, offset, data=None):
101 101 '''
102 102 replace can only replace already committed entries
103 103 that are not pending in the queue
104 104 '''
105 105
106 106 if file not in self.map:
107 107 raise KeyError(file)
108 108 index = self.map[file]
109 109 self.entries[index] = (file, offset, data)
110 110 self.file.write("%s\0%d\n" % (file, offset))
111 111 self.file.flush()
112 112
113 113 @active
114 114 def nest(self):
115 115 self.count += 1
116 116 self.usages += 1
117 117 return self
118 118
119 119 def release(self):
120 120 if self.count > 0:
121 121 self.usages -= 1
122 122 # if the transaction scopes are left without being closed, fail
123 123 if self.count > 0 and self.usages == 0:
124 124 self._abort()
125 125
126 126 def running(self):
127 127 return self.count > 0
128 128
129 129 @active
130 130 def close(self):
131 131 '''commit the transaction'''
132 132 self.count -= 1
133 133 if self.count != 0:
134 134 return
135 135 self.file.close()
136 136 self.entries = []
137 137 if self.after:
138 138 self.after()
139 139 if os.path.isfile(self.journal):
140 140 util.unlink(self.journal)
141 141 self.journal = None
142 142
143 143 @active
144 144 def abort(self):
145 145 '''abort the transaction (generally called on error, or when the
146 146 transaction is not explicitly committed before going out of
147 147 scope)'''
148 148 self._abort()
149 149
150 150 def _abort(self):
151 151 self.count = 0
152 152 self.usages = 0
153 153 self.file.close()
154 154
155 155 try:
156 156 if not self.entries:
157 157 if self.journal:
158 158 util.unlink(self.journal)
159 159 return
160 160
161 161 self.report(_("transaction abort!\n"))
162 162
163 163 try:
164 164 _playback(self.journal, self.report, self.opener,
165 165 self.entries, False)
166 166 self.report(_("rollback completed\n"))
167 167 except Exception:
168 168 self.report(_("rollback failed - please run hg recover\n"))
169 169 finally:
170 170 self.journal = None
171 171
172 172
173 173 def rollback(opener, file, report):
174 174 entries = []
175 175
176 176 fp = util.posixfile(file)
177 177 lines = fp.readlines()
178 178 fp.close()
179 179 for l in lines:
180 180 f, o = l.split('\0')
181 181 entries.append((f, int(o), None))
182 182
183 183 _playback(file, report, opener, entries)
General Comments 0
You need to be logged in to leave comments. Login now