##// END OF EJS Templates
transaction: remove the redundant 'onclose' mechanism...
Pierre-Yves David -
r23512:0ff6b65a default
parent child Browse files
Show More
@@ -1,509 +1,504 b''
1 # transaction.py - simple journaling 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
15 import os
16 import errno
16 import errno
17 import error, util
17 import error, util
18
18
19 version = 2
19 version = 2
20
20
21 def active(func):
21 def active(func):
22 def _active(self, *args, **kwds):
22 def _active(self, *args, **kwds):
23 if self.count == 0:
23 if self.count == 0:
24 raise error.Abort(_(
24 raise error.Abort(_(
25 'cannot use transaction when it is already committed/aborted'))
25 'cannot use transaction when it is already committed/aborted'))
26 return func(self, *args, **kwds)
26 return func(self, *args, **kwds)
27 return _active
27 return _active
28
28
29 def _playback(journal, report, opener, vfsmap, entries, backupentries,
29 def _playback(journal, report, opener, vfsmap, entries, backupentries,
30 unlink=True):
30 unlink=True):
31 for f, o, _ignore in entries:
31 for f, o, _ignore in entries:
32 if o or not unlink:
32 if o or not unlink:
33 try:
33 try:
34 fp = opener(f, 'a')
34 fp = opener(f, 'a')
35 fp.truncate(o)
35 fp.truncate(o)
36 fp.close()
36 fp.close()
37 except IOError:
37 except IOError:
38 report(_("failed to truncate %s\n") % f)
38 report(_("failed to truncate %s\n") % f)
39 raise
39 raise
40 else:
40 else:
41 try:
41 try:
42 opener.unlink(f)
42 opener.unlink(f)
43 except (IOError, OSError), inst:
43 except (IOError, OSError), inst:
44 if inst.errno != errno.ENOENT:
44 if inst.errno != errno.ENOENT:
45 raise
45 raise
46
46
47 backupfiles = []
47 backupfiles = []
48 for l, f, b, c in backupentries:
48 for l, f, b, c in backupentries:
49 if l not in vfsmap and c:
49 if l not in vfsmap and c:
50 report("couldn't handle %s: unknown cache location %s\n"
50 report("couldn't handle %s: unknown cache location %s\n"
51 % (b, l))
51 % (b, l))
52 vfs = vfsmap[l]
52 vfs = vfsmap[l]
53 try:
53 try:
54 if f and b:
54 if f and b:
55 filepath = vfs.join(f)
55 filepath = vfs.join(f)
56 backuppath = vfs.join(b)
56 backuppath = vfs.join(b)
57 try:
57 try:
58 util.copyfile(backuppath, filepath)
58 util.copyfile(backuppath, filepath)
59 backupfiles.append(b)
59 backupfiles.append(b)
60 except IOError:
60 except IOError:
61 report(_("failed to recover %s\n") % f)
61 report(_("failed to recover %s\n") % f)
62 else:
62 else:
63 target = f or b
63 target = f or b
64 try:
64 try:
65 vfs.unlink(target)
65 vfs.unlink(target)
66 except (IOError, OSError), inst:
66 except (IOError, OSError), inst:
67 if inst.errno != errno.ENOENT:
67 if inst.errno != errno.ENOENT:
68 raise
68 raise
69 except (IOError, OSError, util.Abort), inst:
69 except (IOError, OSError, util.Abort), inst:
70 if not c:
70 if not c:
71 raise
71 raise
72
72
73 opener.unlink(journal)
73 opener.unlink(journal)
74 backuppath = "%s.backupfiles" % journal
74 backuppath = "%s.backupfiles" % journal
75 if opener.exists(backuppath):
75 if opener.exists(backuppath):
76 opener.unlink(backuppath)
76 opener.unlink(backuppath)
77 try:
77 try:
78 for f in backupfiles:
78 for f in backupfiles:
79 if opener.exists(f):
79 if opener.exists(f):
80 opener.unlink(f)
80 opener.unlink(f)
81 except (IOError, OSError, util.Abort), inst:
81 except (IOError, OSError, util.Abort), inst:
82 # only pure backup file remains, it is sage to ignore any error
82 # only pure backup file remains, it is sage to ignore any error
83 pass
83 pass
84
84
85 class transaction(object):
85 class transaction(object):
86 def __init__(self, report, opener, vfsmap, journal, after=None,
86 def __init__(self, report, opener, vfsmap, journal, after=None,
87 createmode=None, onclose=None, onabort=None):
87 createmode=None, onabort=None):
88 """Begin a new transaction
88 """Begin a new transaction
89
89
90 Begins a new transaction that allows rolling back writes in the event of
90 Begins a new transaction that allows rolling back writes in the event of
91 an exception.
91 an exception.
92
92
93 * `after`: called after the transaction has been committed
93 * `after`: called after the transaction has been committed
94 * `createmode`: the mode of the journal file that will be created
94 * `createmode`: the mode of the journal file that will be created
95 * `onclose`: called as the transaction is closing, but before it is
96 closed
97 * `onabort`: called as the transaction is aborting, but before any files
95 * `onabort`: called as the transaction is aborting, but before any files
98 have been truncated
96 have been truncated
99 """
97 """
100 self.count = 1
98 self.count = 1
101 self.usages = 1
99 self.usages = 1
102 self.report = report
100 self.report = report
103 # a vfs to the store content
101 # a vfs to the store content
104 self.opener = opener
102 self.opener = opener
105 # a map to access file in various {location -> vfs}
103 # a map to access file in various {location -> vfs}
106 vfsmap = vfsmap.copy()
104 vfsmap = vfsmap.copy()
107 vfsmap[''] = opener # set default value
105 vfsmap[''] = opener # set default value
108 self._vfsmap = vfsmap
106 self._vfsmap = vfsmap
109 self.after = after
107 self.after = after
110 self.onclose = onclose
111 self.onabort = onabort
108 self.onabort = onabort
112 self.entries = []
109 self.entries = []
113 self.map = {}
110 self.map = {}
114 self.journal = journal
111 self.journal = journal
115 self._queue = []
112 self._queue = []
116 # a dict of arguments to be passed to hooks
113 # a dict of arguments to be passed to hooks
117 self.hookargs = {}
114 self.hookargs = {}
118 self.file = opener.open(self.journal, "w")
115 self.file = opener.open(self.journal, "w")
119
116
120 # a list of ('location', 'path', 'backuppath', cache) entries.
117 # a list of ('location', 'path', 'backuppath', cache) entries.
121 # - if 'backuppath' is empty, no file existed at backup time
118 # - if 'backuppath' is empty, no file existed at backup time
122 # - if 'path' is empty, this is a temporary transaction file
119 # - if 'path' is empty, this is a temporary transaction file
123 # - if 'location' is not empty, the path is outside main opener reach.
120 # - if 'location' is not empty, the path is outside main opener reach.
124 # use 'location' value as a key in a vfsmap to find the right 'vfs'
121 # use 'location' value as a key in a vfsmap to find the right 'vfs'
125 # (cache is currently unused)
122 # (cache is currently unused)
126 self._backupentries = []
123 self._backupentries = []
127 self._backupmap = {}
124 self._backupmap = {}
128 self._backupjournal = "%s.backupfiles" % journal
125 self._backupjournal = "%s.backupfiles" % journal
129 self._backupsfile = opener.open(self._backupjournal, 'w')
126 self._backupsfile = opener.open(self._backupjournal, 'w')
130 self._backupsfile.write('%d\n' % version)
127 self._backupsfile.write('%d\n' % version)
131
128
132 if createmode is not None:
129 if createmode is not None:
133 opener.chmod(self.journal, createmode & 0666)
130 opener.chmod(self.journal, createmode & 0666)
134 opener.chmod(self._backupjournal, createmode & 0666)
131 opener.chmod(self._backupjournal, createmode & 0666)
135
132
136 # hold file generations to be performed on commit
133 # hold file generations to be performed on commit
137 self._filegenerators = {}
134 self._filegenerators = {}
138 # hold callbalk to write pending data for hooks
135 # hold callbalk to write pending data for hooks
139 self._pendingcallback = {}
136 self._pendingcallback = {}
140 # True is any pending data have been written ever
137 # True is any pending data have been written ever
141 self._anypending = False
138 self._anypending = False
142 # holds callback to call when writing the transaction
139 # holds callback to call when writing the transaction
143 self._finalizecallback = {}
140 self._finalizecallback = {}
144 # hold callbalk for post transaction close
141 # hold callbalk for post transaction close
145 self._postclosecallback = {}
142 self._postclosecallback = {}
146
143
147 def __del__(self):
144 def __del__(self):
148 if self.journal:
145 if self.journal:
149 self._abort()
146 self._abort()
150
147
151 @active
148 @active
152 def startgroup(self):
149 def startgroup(self):
153 """delay registration of file entry
150 """delay registration of file entry
154
151
155 This is used by strip to delay vision of strip offset. The transaction
152 This is used by strip to delay vision of strip offset. The transaction
156 sees either none or all of the strip actions to be done."""
153 sees either none or all of the strip actions to be done."""
157 self._queue.append([])
154 self._queue.append([])
158
155
159 @active
156 @active
160 def endgroup(self):
157 def endgroup(self):
161 """apply delayed registration of file entry.
158 """apply delayed registration of file entry.
162
159
163 This is used by strip to delay vision of strip offset. The transaction
160 This is used by strip to delay vision of strip offset. The transaction
164 sees either none or all of the strip actions to be done."""
161 sees either none or all of the strip actions to be done."""
165 q = self._queue.pop()
162 q = self._queue.pop()
166 for f, o, data in q:
163 for f, o, data in q:
167 self._addentry(f, o, data)
164 self._addentry(f, o, data)
168
165
169 @active
166 @active
170 def add(self, file, offset, data=None):
167 def add(self, file, offset, data=None):
171 """record the state of an append-only file before update"""
168 """record the state of an append-only file before update"""
172 if file in self.map or file in self._backupmap:
169 if file in self.map or file in self._backupmap:
173 return
170 return
174 if self._queue:
171 if self._queue:
175 self._queue[-1].append((file, offset, data))
172 self._queue[-1].append((file, offset, data))
176 return
173 return
177
174
178 self._addentry(file, offset, data)
175 self._addentry(file, offset, data)
179
176
180 def _addentry(self, file, offset, data):
177 def _addentry(self, file, offset, data):
181 """add a append-only entry to memory and on-disk state"""
178 """add a append-only entry to memory and on-disk state"""
182 if file in self.map or file in self._backupmap:
179 if file in self.map or file in self._backupmap:
183 return
180 return
184 self.entries.append((file, offset, data))
181 self.entries.append((file, offset, data))
185 self.map[file] = len(self.entries) - 1
182 self.map[file] = len(self.entries) - 1
186 # add enough data to the journal to do the truncate
183 # add enough data to the journal to do the truncate
187 self.file.write("%s\0%d\n" % (file, offset))
184 self.file.write("%s\0%d\n" % (file, offset))
188 self.file.flush()
185 self.file.flush()
189
186
190 @active
187 @active
191 def addbackup(self, file, hardlink=True, location=''):
188 def addbackup(self, file, hardlink=True, location=''):
192 """Adds a backup of the file to the transaction
189 """Adds a backup of the file to the transaction
193
190
194 Calling addbackup() creates a hardlink backup of the specified file
191 Calling addbackup() creates a hardlink backup of the specified file
195 that is used to recover the file in the event of the transaction
192 that is used to recover the file in the event of the transaction
196 aborting.
193 aborting.
197
194
198 * `file`: the file path, relative to .hg/store
195 * `file`: the file path, relative to .hg/store
199 * `hardlink`: use a hardlink to quickly create the backup
196 * `hardlink`: use a hardlink to quickly create the backup
200 """
197 """
201 if self._queue:
198 if self._queue:
202 msg = 'cannot use transaction.addbackup inside "group"'
199 msg = 'cannot use transaction.addbackup inside "group"'
203 raise RuntimeError(msg)
200 raise RuntimeError(msg)
204
201
205 if file in self.map or file in self._backupmap:
202 if file in self.map or file in self._backupmap:
206 return
203 return
207 dirname, filename = os.path.split(file)
204 dirname, filename = os.path.split(file)
208 backupfilename = "%s.backup.%s" % (self.journal, filename)
205 backupfilename = "%s.backup.%s" % (self.journal, filename)
209 backupfile = os.path.join(dirname, backupfilename)
206 backupfile = os.path.join(dirname, backupfilename)
210 vfs = self._vfsmap[location]
207 vfs = self._vfsmap[location]
211 if vfs.exists(file):
208 if vfs.exists(file):
212 filepath = vfs.join(file)
209 filepath = vfs.join(file)
213 backuppath = vfs.join(backupfile)
210 backuppath = vfs.join(backupfile)
214 util.copyfiles(filepath, backuppath, hardlink=hardlink)
211 util.copyfiles(filepath, backuppath, hardlink=hardlink)
215 else:
212 else:
216 backupfile = ''
213 backupfile = ''
217
214
218 self._addbackupentry((location, file, backupfile, False))
215 self._addbackupentry((location, file, backupfile, False))
219
216
220 def _addbackupentry(self, entry):
217 def _addbackupentry(self, entry):
221 """register a new backup entry and write it to disk"""
218 """register a new backup entry and write it to disk"""
222 self._backupentries.append(entry)
219 self._backupentries.append(entry)
223 self._backupmap[file] = len(self._backupentries) - 1
220 self._backupmap[file] = len(self._backupentries) - 1
224 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
221 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
225 self._backupsfile.flush()
222 self._backupsfile.flush()
226
223
227 @active
224 @active
228 def registertmp(self, tmpfile, location=''):
225 def registertmp(self, tmpfile, location=''):
229 """register a temporary transaction file
226 """register a temporary transaction file
230
227
231 Such files will be deleted when the transaction exits (on both
228 Such files will be deleted when the transaction exits (on both
232 failure and success).
229 failure and success).
233 """
230 """
234 self._addbackupentry((location, '', tmpfile, False))
231 self._addbackupentry((location, '', tmpfile, False))
235
232
236 @active
233 @active
237 def addfilegenerator(self, genid, filenames, genfunc, order=0,
234 def addfilegenerator(self, genid, filenames, genfunc, order=0,
238 location=''):
235 location=''):
239 """add a function to generates some files at transaction commit
236 """add a function to generates some files at transaction commit
240
237
241 The `genfunc` argument is a function capable of generating proper
238 The `genfunc` argument is a function capable of generating proper
242 content of each entry in the `filename` tuple.
239 content of each entry in the `filename` tuple.
243
240
244 At transaction close time, `genfunc` will be called with one file
241 At transaction close time, `genfunc` will be called with one file
245 object argument per entries in `filenames`.
242 object argument per entries in `filenames`.
246
243
247 The transaction itself is responsible for the backup, creation and
244 The transaction itself is responsible for the backup, creation and
248 final write of such file.
245 final write of such file.
249
246
250 The `genid` argument is used to ensure the same set of file is only
247 The `genid` argument is used to ensure the same set of file is only
251 generated once. Call to `addfilegenerator` for a `genid` already
248 generated once. Call to `addfilegenerator` for a `genid` already
252 present will overwrite the old entry.
249 present will overwrite the old entry.
253
250
254 The `order` argument may be used to control the order in which multiple
251 The `order` argument may be used to control the order in which multiple
255 generator will be executed.
252 generator will be executed.
256
253
257 The `location` arguments may be used to indicate the files are located
254 The `location` arguments may be used to indicate the files are located
258 outside of the the standard directory for transaction. It should match
255 outside of the the standard directory for transaction. It should match
259 one of the key of the `transaction.vfsmap` dictionnary.
256 one of the key of the `transaction.vfsmap` dictionnary.
260 """
257 """
261 # For now, we are unable to do proper backup and restore of custom vfs
258 # For now, we are unable to do proper backup and restore of custom vfs
262 # but for bookmarks that are handled outside this mechanism.
259 # but for bookmarks that are handled outside this mechanism.
263 self._filegenerators[genid] = (order, filenames, genfunc, location)
260 self._filegenerators[genid] = (order, filenames, genfunc, location)
264
261
265 def _generatefiles(self, suffix=''):
262 def _generatefiles(self, suffix=''):
266 # write files registered for generation
263 # write files registered for generation
267 any = False
264 any = False
268 for entry in sorted(self._filegenerators.values()):
265 for entry in sorted(self._filegenerators.values()):
269 any = True
266 any = True
270 order, filenames, genfunc, location = entry
267 order, filenames, genfunc, location = entry
271 vfs = self._vfsmap[location]
268 vfs = self._vfsmap[location]
272 files = []
269 files = []
273 try:
270 try:
274 for name in filenames:
271 for name in filenames:
275 name += suffix
272 name += suffix
276 if suffix:
273 if suffix:
277 self.registertmp(name, location=location)
274 self.registertmp(name, location=location)
278 else:
275 else:
279 self.addbackup(name, location=location)
276 self.addbackup(name, location=location)
280 files.append(vfs(name, 'w', atomictemp=True))
277 files.append(vfs(name, 'w', atomictemp=True))
281 genfunc(*files)
278 genfunc(*files)
282 finally:
279 finally:
283 for f in files:
280 for f in files:
284 f.close()
281 f.close()
285 return any
282 return any
286
283
287 @active
284 @active
288 def find(self, file):
285 def find(self, file):
289 if file in self.map:
286 if file in self.map:
290 return self.entries[self.map[file]]
287 return self.entries[self.map[file]]
291 if file in self._backupmap:
288 if file in self._backupmap:
292 return self._backupentries[self._backupmap[file]]
289 return self._backupentries[self._backupmap[file]]
293 return None
290 return None
294
291
295 @active
292 @active
296 def replace(self, file, offset, data=None):
293 def replace(self, file, offset, data=None):
297 '''
294 '''
298 replace can only replace already committed entries
295 replace can only replace already committed entries
299 that are not pending in the queue
296 that are not pending in the queue
300 '''
297 '''
301
298
302 if file not in self.map:
299 if file not in self.map:
303 raise KeyError(file)
300 raise KeyError(file)
304 index = self.map[file]
301 index = self.map[file]
305 self.entries[index] = (file, offset, data)
302 self.entries[index] = (file, offset, data)
306 self.file.write("%s\0%d\n" % (file, offset))
303 self.file.write("%s\0%d\n" % (file, offset))
307 self.file.flush()
304 self.file.flush()
308
305
309 @active
306 @active
310 def nest(self):
307 def nest(self):
311 self.count += 1
308 self.count += 1
312 self.usages += 1
309 self.usages += 1
313 return self
310 return self
314
311
315 def release(self):
312 def release(self):
316 if self.count > 0:
313 if self.count > 0:
317 self.usages -= 1
314 self.usages -= 1
318 # if the transaction scopes are left without being closed, fail
315 # if the transaction scopes are left without being closed, fail
319 if self.count > 0 and self.usages == 0:
316 if self.count > 0 and self.usages == 0:
320 self._abort()
317 self._abort()
321
318
322 def running(self):
319 def running(self):
323 return self.count > 0
320 return self.count > 0
324
321
325 def addpending(self, category, callback):
322 def addpending(self, category, callback):
326 """add a callback to be called when the transaction is pending
323 """add a callback to be called when the transaction is pending
327
324
328 The transaction will be given as callback's first argument.
325 The transaction will be given as callback's first argument.
329
326
330 Category is a unique identifier to allow overwriting an old callback
327 Category is a unique identifier to allow overwriting an old callback
331 with a newer callback.
328 with a newer callback.
332 """
329 """
333 self._pendingcallback[category] = callback
330 self._pendingcallback[category] = callback
334
331
335 @active
332 @active
336 def writepending(self):
333 def writepending(self):
337 '''write pending file to temporary version
334 '''write pending file to temporary version
338
335
339 This is used to allow hooks to view a transaction before commit'''
336 This is used to allow hooks to view a transaction before commit'''
340 categories = sorted(self._pendingcallback)
337 categories = sorted(self._pendingcallback)
341 for cat in categories:
338 for cat in categories:
342 # remove callback since the data will have been flushed
339 # remove callback since the data will have been flushed
343 any = self._pendingcallback.pop(cat)(self)
340 any = self._pendingcallback.pop(cat)(self)
344 self._anypending = self._anypending or any
341 self._anypending = self._anypending or any
345 self._anypending |= self._generatefiles(suffix='.pending')
342 self._anypending |= self._generatefiles(suffix='.pending')
346 return self._anypending
343 return self._anypending
347
344
348 @active
345 @active
349 def addfinalize(self, category, callback):
346 def addfinalize(self, category, callback):
350 """add a callback to be called when the transaction is closed
347 """add a callback to be called when the transaction is closed
351
348
352 The transaction will be given as callback's first argument.
349 The transaction will be given as callback's first argument.
353
350
354 Category is a unique identifier to allow overwriting old callbacks with
351 Category is a unique identifier to allow overwriting old callbacks with
355 newer callbacks.
352 newer callbacks.
356 """
353 """
357 self._finalizecallback[category] = callback
354 self._finalizecallback[category] = callback
358
355
359 @active
356 @active
360 def addpostclose(self, category, callback):
357 def addpostclose(self, category, callback):
361 """add a callback to be called after the transaction is closed
358 """add a callback to be called after the transaction is closed
362
359
363 The transaction will be given as callback's first argument.
360 The transaction will be given as callback's first argument.
364
361
365 Category is a unique identifier to allow overwriting an old callback
362 Category is a unique identifier to allow overwriting an old callback
366 with a newer callback.
363 with a newer callback.
367 """
364 """
368 self._postclosecallback[category] = callback
365 self._postclosecallback[category] = callback
369
366
370 @active
367 @active
371 def close(self):
368 def close(self):
372 '''commit the transaction'''
369 '''commit the transaction'''
373 if self.count == 1:
370 if self.count == 1:
374 self._generatefiles()
371 self._generatefiles()
375 categories = sorted(self._finalizecallback)
372 categories = sorted(self._finalizecallback)
376 for cat in categories:
373 for cat in categories:
377 self._finalizecallback[cat](self)
374 self._finalizecallback[cat](self)
378 if self.onclose is not None:
379 self.onclose()
380
375
381 self.count -= 1
376 self.count -= 1
382 if self.count != 0:
377 if self.count != 0:
383 return
378 return
384 self.file.close()
379 self.file.close()
385 self._backupsfile.close()
380 self._backupsfile.close()
386 # cleanup temporary files
381 # cleanup temporary files
387 for l, f, b, c in self._backupentries:
382 for l, f, b, c in self._backupentries:
388 if l not in self._vfsmap and c:
383 if l not in self._vfsmap and c:
389 self.report("couldn't remote %s: unknown cache location %s\n"
384 self.report("couldn't remote %s: unknown cache location %s\n"
390 % (b, l))
385 % (b, l))
391 continue
386 continue
392 vfs = self._vfsmap[l]
387 vfs = self._vfsmap[l]
393 if not f and b and vfs.exists(b):
388 if not f and b and vfs.exists(b):
394 try:
389 try:
395 vfs.unlink(b)
390 vfs.unlink(b)
396 except (IOError, OSError, util.Abort), inst:
391 except (IOError, OSError, util.Abort), inst:
397 if not c:
392 if not c:
398 raise
393 raise
399 # Abort may be raise by read only opener
394 # Abort may be raise by read only opener
400 self.report("couldn't remote %s: %s\n"
395 self.report("couldn't remote %s: %s\n"
401 % (vfs.join(b), inst))
396 % (vfs.join(b), inst))
402 self.entries = []
397 self.entries = []
403 if self.after:
398 if self.after:
404 self.after()
399 self.after()
405 if self.opener.isfile(self.journal):
400 if self.opener.isfile(self.journal):
406 self.opener.unlink(self.journal)
401 self.opener.unlink(self.journal)
407 if self.opener.isfile(self._backupjournal):
402 if self.opener.isfile(self._backupjournal):
408 self.opener.unlink(self._backupjournal)
403 self.opener.unlink(self._backupjournal)
409 for _l, _f, b, c in self._backupentries:
404 for _l, _f, b, c in self._backupentries:
410 if l not in self._vfsmap and c:
405 if l not in self._vfsmap and c:
411 self.report("couldn't remote %s: unknown cache location"
406 self.report("couldn't remote %s: unknown cache location"
412 "%s\n" % (b, l))
407 "%s\n" % (b, l))
413 continue
408 continue
414 vfs = self._vfsmap[l]
409 vfs = self._vfsmap[l]
415 if b and vfs.exists(b):
410 if b and vfs.exists(b):
416 try:
411 try:
417 vfs.unlink(b)
412 vfs.unlink(b)
418 except (IOError, OSError, util.Abort), inst:
413 except (IOError, OSError, util.Abort), inst:
419 if not c:
414 if not c:
420 raise
415 raise
421 # Abort may be raise by read only opener
416 # Abort may be raise by read only opener
422 self.report("couldn't remote %s: %s\n"
417 self.report("couldn't remote %s: %s\n"
423 % (vfs.join(b), inst))
418 % (vfs.join(b), inst))
424 self._backupentries = []
419 self._backupentries = []
425 self.journal = None
420 self.journal = None
426 # run post close action
421 # run post close action
427 categories = sorted(self._postclosecallback)
422 categories = sorted(self._postclosecallback)
428 for cat in categories:
423 for cat in categories:
429 self._postclosecallback[cat](self)
424 self._postclosecallback[cat](self)
430
425
431 @active
426 @active
432 def abort(self):
427 def abort(self):
433 '''abort the transaction (generally called on error, or when the
428 '''abort the transaction (generally called on error, or when the
434 transaction is not explicitly committed before going out of
429 transaction is not explicitly committed before going out of
435 scope)'''
430 scope)'''
436 self._abort()
431 self._abort()
437
432
438 def _abort(self):
433 def _abort(self):
439 self.count = 0
434 self.count = 0
440 self.usages = 0
435 self.usages = 0
441 self.file.close()
436 self.file.close()
442 self._backupsfile.close()
437 self._backupsfile.close()
443
438
444 if self.onabort is not None:
439 if self.onabort is not None:
445 self.onabort()
440 self.onabort()
446
441
447 try:
442 try:
448 if not self.entries and not self._backupentries:
443 if not self.entries and not self._backupentries:
449 if self.journal:
444 if self.journal:
450 self.opener.unlink(self.journal)
445 self.opener.unlink(self.journal)
451 if self._backupjournal:
446 if self._backupjournal:
452 self.opener.unlink(self._backupjournal)
447 self.opener.unlink(self._backupjournal)
453 return
448 return
454
449
455 self.report(_("transaction abort!\n"))
450 self.report(_("transaction abort!\n"))
456
451
457 try:
452 try:
458 _playback(self.journal, self.report, self.opener, self._vfsmap,
453 _playback(self.journal, self.report, self.opener, self._vfsmap,
459 self.entries, self._backupentries, False)
454 self.entries, self._backupentries, False)
460 self.report(_("rollback completed\n"))
455 self.report(_("rollback completed\n"))
461 except Exception:
456 except Exception:
462 self.report(_("rollback failed - please run hg recover\n"))
457 self.report(_("rollback failed - please run hg recover\n"))
463 finally:
458 finally:
464 self.journal = None
459 self.journal = None
465
460
466
461
467 def rollback(opener, vfsmap, file, report):
462 def rollback(opener, vfsmap, file, report):
468 """Rolls back the transaction contained in the given file
463 """Rolls back the transaction contained in the given file
469
464
470 Reads the entries in the specified file, and the corresponding
465 Reads the entries in the specified file, and the corresponding
471 '*.backupfiles' file, to recover from an incomplete transaction.
466 '*.backupfiles' file, to recover from an incomplete transaction.
472
467
473 * `file`: a file containing a list of entries, specifying where
468 * `file`: a file containing a list of entries, specifying where
474 to truncate each file. The file should contain a list of
469 to truncate each file. The file should contain a list of
475 file\0offset pairs, delimited by newlines. The corresponding
470 file\0offset pairs, delimited by newlines. The corresponding
476 '*.backupfiles' file should contain a list of file\0backupfile
471 '*.backupfiles' file should contain a list of file\0backupfile
477 pairs, delimited by \0.
472 pairs, delimited by \0.
478 """
473 """
479 entries = []
474 entries = []
480 backupentries = []
475 backupentries = []
481
476
482 fp = opener.open(file)
477 fp = opener.open(file)
483 lines = fp.readlines()
478 lines = fp.readlines()
484 fp.close()
479 fp.close()
485 for l in lines:
480 for l in lines:
486 try:
481 try:
487 f, o = l.split('\0')
482 f, o = l.split('\0')
488 entries.append((f, int(o), None))
483 entries.append((f, int(o), None))
489 except ValueError:
484 except ValueError:
490 report(_("couldn't read journal entry %r!\n") % l)
485 report(_("couldn't read journal entry %r!\n") % l)
491
486
492 backupjournal = "%s.backupfiles" % file
487 backupjournal = "%s.backupfiles" % file
493 if opener.exists(backupjournal):
488 if opener.exists(backupjournal):
494 fp = opener.open(backupjournal)
489 fp = opener.open(backupjournal)
495 lines = fp.readlines()
490 lines = fp.readlines()
496 if lines:
491 if lines:
497 ver = lines[0][:-1]
492 ver = lines[0][:-1]
498 if ver == str(version):
493 if ver == str(version):
499 for line in lines[1:]:
494 for line in lines[1:]:
500 if line:
495 if line:
501 # Shave off the trailing newline
496 # Shave off the trailing newline
502 line = line[:-1]
497 line = line[:-1]
503 l, f, b, c = line.split('\0')
498 l, f, b, c = line.split('\0')
504 backupentries.append((l, f, b, bool(c)))
499 backupentries.append((l, f, b, bool(c)))
505 else:
500 else:
506 report(_("journal was created by a different version of "
501 report(_("journal was created by a different version of "
507 "Mercurial"))
502 "Mercurial"))
508
503
509 _playback(file, report, opener, vfsmap, entries, backupentries)
504 _playback(file, report, opener, vfsmap, entries, backupentries)
@@ -1,281 +1,281 b''
1 Init repo1:
1 Init repo1:
2
2
3 $ hg init repo1
3 $ hg init repo1
4 $ cd repo1
4 $ cd repo1
5 $ echo "some text" > a
5 $ echo "some text" > a
6 $ hg add
6 $ hg add
7 adding a
7 adding a
8 $ hg ci -m first
8 $ hg ci -m first
9 $ cat .hg/store/fncache | sort
9 $ cat .hg/store/fncache | sort
10 data/a.i
10 data/a.i
11
11
12 Testing a.i/b:
12 Testing a.i/b:
13
13
14 $ mkdir a.i
14 $ mkdir a.i
15 $ echo "some other text" > a.i/b
15 $ echo "some other text" > a.i/b
16 $ hg add
16 $ hg add
17 adding a.i/b (glob)
17 adding a.i/b (glob)
18 $ hg ci -m second
18 $ hg ci -m second
19 $ cat .hg/store/fncache | sort
19 $ cat .hg/store/fncache | sort
20 data/a.i
20 data/a.i
21 data/a.i.hg/b.i
21 data/a.i.hg/b.i
22
22
23 Testing a.i.hg/c:
23 Testing a.i.hg/c:
24
24
25 $ mkdir a.i.hg
25 $ mkdir a.i.hg
26 $ echo "yet another text" > a.i.hg/c
26 $ echo "yet another text" > a.i.hg/c
27 $ hg add
27 $ hg add
28 adding a.i.hg/c (glob)
28 adding a.i.hg/c (glob)
29 $ hg ci -m third
29 $ hg ci -m third
30 $ cat .hg/store/fncache | sort
30 $ cat .hg/store/fncache | sort
31 data/a.i
31 data/a.i
32 data/a.i.hg.hg/c.i
32 data/a.i.hg.hg/c.i
33 data/a.i.hg/b.i
33 data/a.i.hg/b.i
34
34
35 Testing verify:
35 Testing verify:
36
36
37 $ hg verify
37 $ hg verify
38 checking changesets
38 checking changesets
39 checking manifests
39 checking manifests
40 crosschecking files in changesets and manifests
40 crosschecking files in changesets and manifests
41 checking files
41 checking files
42 3 files, 3 changesets, 3 total revisions
42 3 files, 3 changesets, 3 total revisions
43
43
44 $ rm .hg/store/fncache
44 $ rm .hg/store/fncache
45
45
46 $ hg verify
46 $ hg verify
47 checking changesets
47 checking changesets
48 checking manifests
48 checking manifests
49 crosschecking files in changesets and manifests
49 crosschecking files in changesets and manifests
50 checking files
50 checking files
51 data/a.i@0: missing revlog!
51 data/a.i@0: missing revlog!
52 data/a.i.hg/c.i@2: missing revlog!
52 data/a.i.hg/c.i@2: missing revlog!
53 data/a.i/b.i@1: missing revlog!
53 data/a.i/b.i@1: missing revlog!
54 3 files, 3 changesets, 3 total revisions
54 3 files, 3 changesets, 3 total revisions
55 3 integrity errors encountered!
55 3 integrity errors encountered!
56 (first damaged changeset appears to be 0)
56 (first damaged changeset appears to be 0)
57 [1]
57 [1]
58 $ cd ..
58 $ cd ..
59
59
60 Non store repo:
60 Non store repo:
61
61
62 $ hg --config format.usestore=False init foo
62 $ hg --config format.usestore=False init foo
63 $ cd foo
63 $ cd foo
64 $ mkdir tst.d
64 $ mkdir tst.d
65 $ echo foo > tst.d/foo
65 $ echo foo > tst.d/foo
66 $ hg ci -Amfoo
66 $ hg ci -Amfoo
67 adding tst.d/foo
67 adding tst.d/foo
68 $ find .hg | sort
68 $ find .hg | sort
69 .hg
69 .hg
70 .hg/00changelog.i
70 .hg/00changelog.i
71 .hg/00manifest.i
71 .hg/00manifest.i
72 .hg/cache
72 .hg/cache
73 .hg/cache/branch2-served
73 .hg/cache/branch2-served
74 .hg/data
74 .hg/data
75 .hg/data/tst.d.hg
75 .hg/data/tst.d.hg
76 .hg/data/tst.d.hg/foo.i
76 .hg/data/tst.d.hg/foo.i
77 .hg/dirstate
77 .hg/dirstate
78 .hg/last-message.txt
78 .hg/last-message.txt
79 .hg/phaseroots
79 .hg/phaseroots
80 .hg/requires
80 .hg/requires
81 .hg/undo
81 .hg/undo
82 .hg/undo.bookmarks
82 .hg/undo.bookmarks
83 .hg/undo.branch
83 .hg/undo.branch
84 .hg/undo.desc
84 .hg/undo.desc
85 .hg/undo.dirstate
85 .hg/undo.dirstate
86 .hg/undo.phaseroots
86 .hg/undo.phaseroots
87 $ cd ..
87 $ cd ..
88
88
89 Non fncache repo:
89 Non fncache repo:
90
90
91 $ hg --config format.usefncache=False init bar
91 $ hg --config format.usefncache=False init bar
92 $ cd bar
92 $ cd bar
93 $ mkdir tst.d
93 $ mkdir tst.d
94 $ echo foo > tst.d/Foo
94 $ echo foo > tst.d/Foo
95 $ hg ci -Amfoo
95 $ hg ci -Amfoo
96 adding tst.d/Foo
96 adding tst.d/Foo
97 $ find .hg | sort
97 $ find .hg | sort
98 .hg
98 .hg
99 .hg/00changelog.i
99 .hg/00changelog.i
100 .hg/cache
100 .hg/cache
101 .hg/cache/branch2-served
101 .hg/cache/branch2-served
102 .hg/dirstate
102 .hg/dirstate
103 .hg/last-message.txt
103 .hg/last-message.txt
104 .hg/requires
104 .hg/requires
105 .hg/store
105 .hg/store
106 .hg/store/00changelog.i
106 .hg/store/00changelog.i
107 .hg/store/00manifest.i
107 .hg/store/00manifest.i
108 .hg/store/data
108 .hg/store/data
109 .hg/store/data/tst.d.hg
109 .hg/store/data/tst.d.hg
110 .hg/store/data/tst.d.hg/_foo.i
110 .hg/store/data/tst.d.hg/_foo.i
111 .hg/store/phaseroots
111 .hg/store/phaseroots
112 .hg/store/undo
112 .hg/store/undo
113 .hg/store/undo.phaseroots
113 .hg/store/undo.phaseroots
114 .hg/undo.bookmarks
114 .hg/undo.bookmarks
115 .hg/undo.branch
115 .hg/undo.branch
116 .hg/undo.desc
116 .hg/undo.desc
117 .hg/undo.dirstate
117 .hg/undo.dirstate
118 $ cd ..
118 $ cd ..
119
119
120 Encoding of reserved / long paths in the store
120 Encoding of reserved / long paths in the store
121
121
122 $ hg init r2
122 $ hg init r2
123 $ cd r2
123 $ cd r2
124 $ cat <<EOF > .hg/hgrc
124 $ cat <<EOF > .hg/hgrc
125 > [ui]
125 > [ui]
126 > portablefilenames = ignore
126 > portablefilenames = ignore
127 > EOF
127 > EOF
128
128
129 $ hg import -q --bypass - <<EOF
129 $ hg import -q --bypass - <<EOF
130 > # HG changeset patch
130 > # HG changeset patch
131 > # User test
131 > # User test
132 > # Date 0 0
132 > # Date 0 0
133 > # Node ID 1c7a2f7cb77be1a0def34e4c7cabc562ad98fbd7
133 > # Node ID 1c7a2f7cb77be1a0def34e4c7cabc562ad98fbd7
134 > # Parent 0000000000000000000000000000000000000000
134 > # Parent 0000000000000000000000000000000000000000
135 > 1
135 > 1
136 >
136 >
137 > diff --git a/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz b/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz
137 > diff --git a/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz b/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz
138 > new file mode 100644
138 > new file mode 100644
139 > --- /dev/null
139 > --- /dev/null
140 > +++ b/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz
140 > +++ b/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz
141 > @@ -0,0 +1,1 @@
141 > @@ -0,0 +1,1 @@
142 > +foo
142 > +foo
143 > diff --git a/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT b/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT
143 > diff --git a/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT b/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT
144 > new file mode 100644
144 > new file mode 100644
145 > --- /dev/null
145 > --- /dev/null
146 > +++ b/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT
146 > +++ b/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT
147 > @@ -0,0 +1,1 @@
147 > @@ -0,0 +1,1 @@
148 > +foo
148 > +foo
149 > diff --git a/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
149 > diff --git a/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
150 > new file mode 100644
150 > new file mode 100644
151 > --- /dev/null
151 > --- /dev/null
152 > +++ b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
152 > +++ b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
153 > @@ -0,0 +1,1 @@
153 > @@ -0,0 +1,1 @@
154 > +foo
154 > +foo
155 > diff --git a/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c b/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c
155 > diff --git a/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c b/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c
156 > new file mode 100644
156 > new file mode 100644
157 > --- /dev/null
157 > --- /dev/null
158 > +++ b/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c
158 > +++ b/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c
159 > @@ -0,0 +1,1 @@
159 > @@ -0,0 +1,1 @@
160 > +foo
160 > +foo
161 > diff --git a/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider b/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider
161 > diff --git a/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider b/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider
162 > new file mode 100644
162 > new file mode 100644
163 > --- /dev/null
163 > --- /dev/null
164 > +++ b/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider
164 > +++ b/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider
165 > @@ -0,0 +1,1 @@
165 > @@ -0,0 +1,1 @@
166 > +foo
166 > +foo
167 > EOF
167 > EOF
168
168
169 $ find .hg/store -name *.i | sort
169 $ find .hg/store -name *.i | sort
170 .hg/store/00changelog.i
170 .hg/store/00changelog.i
171 .hg/store/00manifest.i
171 .hg/store/00manifest.i
172 .hg/store/data/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i
172 .hg/store/data/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i
173 .hg/store/dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxx168e07b38e65eff86ab579afaaa8e30bfbe0f35f.i
173 .hg/store/dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxx168e07b38e65eff86ab579afaaa8e30bfbe0f35f.i
174 .hg/store/dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i
174 .hg/store/dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i
175 .hg/store/dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i
175 .hg/store/dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i
176 .hg/store/dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilename0d8e1f4187c650e2f1fdca9fd90f786bc0976b6b.i
176 .hg/store/dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilename0d8e1f4187c650e2f1fdca9fd90f786bc0976b6b.i
177
177
178 $ cd ..
178 $ cd ..
179
179
180 Aborting lock does not prevent fncache writes
180 Aborting lock does not prevent fncache writes
181
181
182 $ cat > exceptionext.py <<EOF
182 $ cat > exceptionext.py <<EOF
183 > import os
183 > import os
184 > from mercurial import commands, util
184 > from mercurial import commands, util
185 > from mercurial.extensions import wrapfunction
185 > from mercurial.extensions import wrapfunction
186 >
186 >
187 > def lockexception(orig, vfs, lockname, wait, releasefn, acquirefn, desc):
187 > def lockexception(orig, vfs, lockname, wait, releasefn, acquirefn, desc):
188 > def releasewrap():
188 > def releasewrap():
189 > raise util.Abort("forced lock failure")
189 > raise util.Abort("forced lock failure")
190 > return orig(vfs, lockname, wait, releasewrap, acquirefn, desc)
190 > return orig(vfs, lockname, wait, releasewrap, acquirefn, desc)
191 >
191 >
192 > def reposetup(ui, repo):
192 > def reposetup(ui, repo):
193 > wrapfunction(repo, '_lock', lockexception)
193 > wrapfunction(repo, '_lock', lockexception)
194 >
194 >
195 > cmdtable = {}
195 > cmdtable = {}
196 >
196 >
197 > EOF
197 > EOF
198 $ extpath=`pwd`/exceptionext.py
198 $ extpath=`pwd`/exceptionext.py
199 $ hg init fncachetxn
199 $ hg init fncachetxn
200 $ cd fncachetxn
200 $ cd fncachetxn
201 $ printf "[extensions]\nexceptionext=$extpath\n" >> .hg/hgrc
201 $ printf "[extensions]\nexceptionext=$extpath\n" >> .hg/hgrc
202 $ touch y
202 $ touch y
203 $ hg ci -qAm y
203 $ hg ci -qAm y
204 abort: forced lock failure
204 abort: forced lock failure
205 [255]
205 [255]
206 $ cat .hg/store/fncache
206 $ cat .hg/store/fncache
207 data/y.i
207 data/y.i
208
208
209 Aborting transaction prevents fncache change
209 Aborting transaction prevents fncache change
210
210
211 $ cat > ../exceptionext.py <<EOF
211 $ cat > ../exceptionext.py <<EOF
212 > import os
212 > import os
213 > from mercurial import commands, util, localrepo
213 > from mercurial import commands, util, localrepo
214 > from mercurial.extensions import wrapfunction
214 > from mercurial.extensions import wrapfunction
215 >
215 >
216 > def wrapper(orig, self, *args, **kwargs):
216 > def wrapper(orig, self, *args, **kwargs):
217 > tr = orig(self, *args, **kwargs)
217 > tr = orig(self, *args, **kwargs)
218 > def fail(tr):
218 > def fail(tr):
219 > raise util.Abort("forced transaction failure")
219 > raise util.Abort("forced transaction failure")
220 > # zzz prefix to ensure it sorted after store.write
220 > # zzz prefix to ensure it sorted after store.write
221 > tr.addfinalize('zzz-forcefails', fail)
221 > tr.addfinalize('zzz-forcefails', fail)
222 > return tr
222 > return tr
223 >
223 >
224 > def uisetup(ui):
224 > def uisetup(ui):
225 > wrapfunction(localrepo.localrepository, 'transaction', wrapper)
225 > wrapfunction(localrepo.localrepository, 'transaction', wrapper)
226 >
226 >
227 > cmdtable = {}
227 > cmdtable = {}
228 >
228 >
229 > EOF
229 > EOF
230 $ rm -f "${extpath}c"
230 $ rm -f "${extpath}c"
231 $ touch z
231 $ touch z
232 $ hg ci -qAm z
232 $ hg ci -qAm z
233 transaction abort!
233 transaction abort!
234 rollback completed
234 rollback completed
235 abort: forced transaction failure
235 abort: forced transaction failure
236 [255]
236 [255]
237 $ cat .hg/store/fncache
237 $ cat .hg/store/fncache
238 data/y.i
238 data/y.i
239
239
240 Aborted transactions can be recovered later
240 Aborted transactions can be recovered later
241
241
242 $ cat > ../exceptionext.py <<EOF
242 $ cat > ../exceptionext.py <<EOF
243 > import os
243 > import os
244 > from mercurial import commands, util, transaction
244 > from mercurial import commands, util, transaction, localrepo
245 > from mercurial.extensions import wrapfunction
245 > from mercurial.extensions import wrapfunction
246 >
246 >
247 > def closewrapper(orig, self, *args, **kwargs):
247 > def trwrapper(orig, self, *args, **kwargs):
248 > origonclose = self.onclose
248 > tr = orig(self, *args, **kwargs)
249 > def onclose():
249 > def fail(tr):
250 > origonclose()
251 > raise util.Abort("forced transaction failure")
250 > raise util.Abort("forced transaction failure")
252 > self.onclose = onclose
251 > # zzz prefix to ensure it sorted after store.write
253 > return orig(self, *args, **kwargs)
252 > tr.addfinalize('zzz-forcefails', fail)
253 > return tr
254 >
254 >
255 > def abortwrapper(orig, self, *args, **kwargs):
255 > def abortwrapper(orig, self, *args, **kwargs):
256 > raise util.Abort("forced transaction failure")
256 > raise util.Abort("forced transaction failure")
257 >
257 >
258 > def uisetup(ui):
258 > def uisetup(ui):
259 > wrapfunction(transaction.transaction, 'close', closewrapper)
259 > wrapfunction(localrepo.localrepository, 'transaction', trwrapper)
260 > wrapfunction(transaction.transaction, '_abort', abortwrapper)
260 > wrapfunction(transaction.transaction, '_abort', abortwrapper)
261 >
261 >
262 > cmdtable = {}
262 > cmdtable = {}
263 >
263 >
264 > EOF
264 > EOF
265 $ rm -f "${extpath}c"
265 $ rm -f "${extpath}c"
266 $ hg up -q 1
266 $ hg up -q 1
267 $ touch z
267 $ touch z
268 $ hg ci -qAm z 2>/dev/null
268 $ hg ci -qAm z 2>/dev/null
269 [255]
269 [255]
270 $ cat .hg/store/fncache | sort
270 $ cat .hg/store/fncache | sort
271 data/y.i
271 data/y.i
272 data/z.i
272 data/z.i
273 $ hg recover
273 $ hg recover
274 rolling back interrupted transaction
274 rolling back interrupted transaction
275 checking changesets
275 checking changesets
276 checking manifests
276 checking manifests
277 crosschecking files in changesets and manifests
277 crosschecking files in changesets and manifests
278 checking files
278 checking files
279 1 files, 1 changesets, 1 total revisions
279 1 files, 1 changesets, 1 total revisions
280 $ cat .hg/store/fncache
280 $ cat .hg/store/fncache
281 data/y.i
281 data/y.i
General Comments 0
You need to be logged in to leave comments. Login now