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