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