##// END OF EJS Templates
transaction: add support for non-append files...
Durham Goode -
r20882:5dffd06f default
parent child Browse files
Show More
@@ -12,8 +12,8 b''
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 errno
15 import errno, os
16 import error
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):
@@ -23,7 +23,7 b' def active(func):'
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, backupentries, 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:
@@ -39,7 +39,24 b' def _playback(journal, report, opener, e'
39 except (IOError, OSError), inst:
39 except (IOError, OSError), inst:
40 if inst.errno != errno.ENOENT:
40 if inst.errno != errno.ENOENT:
41 raise
41 raise
42
43 backupfiles = []
44 for f, b, ignore in backupentries:
45 filepath = opener.join(f)
46 backuppath = opener.join(b)
47 try:
48 util.copyfile(backuppath, filepath)
49 backupfiles.append(b)
50 except IOError:
51 report(_("failed to recover %s\n") % f)
52 raise
53
42 opener.unlink(journal)
54 opener.unlink(journal)
55 backuppath = "%s.backupfiles" % journal
56 if opener.exists(backuppath):
57 opener.unlink(backuppath)
58 for f in backupfiles:
59 opener.unlink(f)
43
60
44 class transaction(object):
61 class transaction(object):
45 def __init__(self, report, opener, journal, after=None, createmode=None,
62 def __init__(self, report, opener, journal, after=None, createmode=None,
@@ -64,13 +81,18 b' class transaction(object):'
64 self.onclose = onclose
81 self.onclose = onclose
65 self.onabort = onabort
82 self.onabort = onabort
66 self.entries = []
83 self.entries = []
84 self.backupentries = []
67 self.map = {}
85 self.map = {}
86 self.backupmap = {}
68 self.journal = journal
87 self.journal = journal
69 self._queue = []
88 self._queue = []
70
89
90 self.backupjournal = "%s.backupfiles" % journal
71 self.file = opener.open(self.journal, "w")
91 self.file = opener.open(self.journal, "w")
92 self.backupsfile = opener.open(self.backupjournal, 'w')
72 if createmode is not None:
93 if createmode is not None:
73 opener.chmod(self.journal, createmode & 0666)
94 opener.chmod(self.journal, createmode & 0666)
95 opener.chmod(self.backupjournal, createmode & 0666)
74
96
75 def __del__(self):
97 def __del__(self):
76 if self.journal:
98 if self.journal:
@@ -78,22 +100,36 b' class transaction(object):'
78
100
79 @active
101 @active
80 def startgroup(self):
102 def startgroup(self):
81 self._queue.append([])
103 self._queue.append(([], []))
82
104
83 @active
105 @active
84 def endgroup(self):
106 def endgroup(self):
85 q = self._queue.pop()
107 q = self._queue.pop()
86 d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q])
108 self.entries.extend(q[0])
87 self.entries.extend(q)
109 self.backupentries.extend(q[1])
110
111 offsets = []
112 backups = []
113 for f, o, _ in q[0]:
114 offsets.append((f, o))
115
116 for f, b, _ in q[1]:
117 backups.append((f, b))
118
119 d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
88 self.file.write(d)
120 self.file.write(d)
89 self.file.flush()
121 self.file.flush()
90
122
123 d = ''.join(['%s\0%s\0' % (f, b) for f, b in backups])
124 self.backupsfile.write(d)
125 self.backupsfile.flush()
126
91 @active
127 @active
92 def add(self, file, offset, data=None):
128 def add(self, file, offset, data=None):
93 if file in self.map:
129 if file in self.map or file in self.backupmap:
94 return
130 return
95 if self._queue:
131 if self._queue:
96 self._queue[-1].append((file, offset, data))
132 self._queue[-1][0].append((file, offset, data))
97 return
133 return
98
134
99 self.entries.append((file, offset, data))
135 self.entries.append((file, offset, data))
@@ -103,9 +139,43 b' class transaction(object):'
103 self.file.flush()
139 self.file.flush()
104
140
105 @active
141 @active
142 def addbackup(self, file, hardlink=True):
143 """Adds a backup of the file to the transaction
144
145 Calling addbackup() creates a hardlink backup of the specified file
146 that is used to recover the file in the event of the transaction
147 aborting.
148
149 * `file`: the file path, relative to .hg/store
150 * `hardlink`: use a hardlink to quickly create the backup
151 """
152
153 if file in self.map or file in self.backupmap:
154 return
155 backupfile = "journal.%s" % file
156 if self.opener.exists(file):
157 filepath = self.opener.join(file)
158 backuppath = self.opener.join(backupfile)
159 util.copyfiles(filepath, backuppath, hardlink=hardlink)
160 else:
161 self.add(file, 0)
162 return
163
164 if self._queue:
165 self._queue[-1][1].append((file, backupfile))
166 return
167
168 self.backupentries.append((file, backupfile, None))
169 self.backupmap[file] = len(self.backupentries) - 1
170 self.backupsfile.write("%s\0%s\0" % (file, backupfile))
171 self.backupsfile.flush()
172
173 @active
106 def find(self, file):
174 def find(self, file):
107 if file in self.map:
175 if file in self.map:
108 return self.entries[self.map[file]]
176 return self.entries[self.map[file]]
177 if file in self.backupmap:
178 return self.backupentries[self.backupmap[file]]
109 return None
179 return None
110
180
111 @active
181 @active
@@ -153,6 +223,11 b' class transaction(object):'
153 self.after()
223 self.after()
154 if self.opener.isfile(self.journal):
224 if self.opener.isfile(self.journal):
155 self.opener.unlink(self.journal)
225 self.opener.unlink(self.journal)
226 if self.opener.isfile(self.backupjournal):
227 self.opener.unlink(self.backupjournal)
228 for f, b, _ in self.backupentries:
229 self.opener.unlink(b)
230 self.backupentries = []
156 self.journal = None
231 self.journal = None
157
232
158 @active
233 @active
@@ -171,16 +246,18 b' class transaction(object):'
171 self.onabort()
246 self.onabort()
172
247
173 try:
248 try:
174 if not self.entries:
249 if not self.entries and not self.backupentries:
175 if self.journal:
250 if self.journal:
176 self.opener.unlink(self.journal)
251 self.opener.unlink(self.journal)
252 if self.backupjournal:
253 self.opener.unlink(self.backupjournal)
177 return
254 return
178
255
179 self.report(_("transaction abort!\n"))
256 self.report(_("transaction abort!\n"))
180
257
181 try:
258 try:
182 _playback(self.journal, self.report, self.opener,
259 _playback(self.journal, self.report, self.opener,
183 self.entries, False)
260 self.entries, self.backupentries, False)
184 self.report(_("rollback completed\n"))
261 self.report(_("rollback completed\n"))
185 except Exception:
262 except Exception:
186 self.report(_("rollback failed - please run hg recover\n"))
263 self.report(_("rollback failed - please run hg recover\n"))
@@ -189,7 +266,19 b' class transaction(object):'
189
266
190
267
191 def rollback(opener, file, report):
268 def rollback(opener, file, report):
269 """Rolls back the transaction contained in the given file
270
271 Reads the entries in the specified file, and the corresponding
272 '*.backupfiles' file, to recover from an incomplete transaction.
273
274 * `file`: a file containing a list of entries, specifying where
275 to truncate each file. The file should contain a list of
276 file\0offset pairs, delimited by newlines. The corresponding
277 '*.backupfiles' file should contain a list of file\0backupfile
278 pairs, delimited by \0.
279 """
192 entries = []
280 entries = []
281 backupentries = []
193
282
194 fp = opener.open(file)
283 fp = opener.open(file)
195 lines = fp.readlines()
284 lines = fp.readlines()
@@ -201,4 +290,14 b' def rollback(opener, file, report):'
201 except ValueError:
290 except ValueError:
202 report(_("couldn't read journal entry %r!\n") % l)
291 report(_("couldn't read journal entry %r!\n") % l)
203
292
204 _playback(file, report, opener, entries)
293 backupjournal = "%s.backupfiles" % file
294 if opener.exists(backupjournal):
295 fp = opener.open(backupjournal)
296 data = fp.read()
297 if len(data) > 0:
298 parts = data.split('\0')
299 for i in xrange(0, len(parts), 2):
300 f, b = parts[i:i + 1]
301 backupentries.append((f, b, None))
302
303 _playback(file, report, opener, entries, backupentries)
General Comments 0
You need to be logged in to leave comments. Login now