##// END OF EJS Templates
transactions: fix hg recover with fncache backups...
Durham Goode -
r23063:cd86a670 stable
parent child Browse files
Show More
@@ -1,356 +1,357 b''
1 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 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, backupentries, 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 opener.unlink(f)
39 39 except (IOError, OSError), inst:
40 40 if inst.errno != errno.ENOENT:
41 41 raise
42 42
43 43 backupfiles = []
44 44 for f, b, _ignore in backupentries:
45 45 filepath = opener.join(f)
46 46 backuppath = opener.join(b)
47 47 try:
48 48 util.copyfile(backuppath, filepath)
49 49 backupfiles.append(b)
50 50 except IOError:
51 51 report(_("failed to recover %s\n") % f)
52 52 raise
53 53
54 54 opener.unlink(journal)
55 55 backuppath = "%s.backupfiles" % journal
56 56 if opener.exists(backuppath):
57 57 opener.unlink(backuppath)
58 58 for f in backupfiles:
59 59 opener.unlink(f)
60 60
61 61 class transaction(object):
62 62 def __init__(self, report, opener, journal, after=None, createmode=None,
63 63 onclose=None, onabort=None):
64 64 """Begin a new transaction
65 65
66 66 Begins a new transaction that allows rolling back writes in the event of
67 67 an exception.
68 68
69 69 * `after`: called after the transaction has been committed
70 70 * `createmode`: the mode of the journal file that will be created
71 71 * `onclose`: called as the transaction is closing, but before it is
72 72 closed
73 73 * `onabort`: called as the transaction is aborting, but before any files
74 74 have been truncated
75 75 """
76 76 self.count = 1
77 77 self.usages = 1
78 78 self.report = report
79 79 self.opener = opener
80 80 self.after = after
81 81 self.onclose = onclose
82 82 self.onabort = onabort
83 83 self.entries = []
84 84 self.backupentries = []
85 85 self.map = {}
86 86 self.backupmap = {}
87 87 self.journal = journal
88 88 self._queue = []
89 89 # a dict of arguments to be passed to hooks
90 90 self.hookargs = {}
91 91
92 92 self.backupjournal = "%s.backupfiles" % journal
93 93 self.file = opener.open(self.journal, "w")
94 94 self.backupsfile = opener.open(self.backupjournal, 'w')
95 95 if createmode is not None:
96 96 opener.chmod(self.journal, createmode & 0666)
97 97 opener.chmod(self.backupjournal, createmode & 0666)
98 98
99 99 # hold file generations to be performed on commit
100 100 self._filegenerators = {}
101 101
102 102 def __del__(self):
103 103 if self.journal:
104 104 self._abort()
105 105
106 106 @active
107 107 def startgroup(self):
108 108 self._queue.append(([], []))
109 109
110 110 @active
111 111 def endgroup(self):
112 112 q = self._queue.pop()
113 113 self.entries.extend(q[0])
114 114 self.backupentries.extend(q[1])
115 115
116 116 offsets = []
117 117 backups = []
118 118 for f, o, _data in q[0]:
119 119 offsets.append((f, o))
120 120
121 121 for f, b, _data in q[1]:
122 122 backups.append((f, b))
123 123
124 124 d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
125 125 self.file.write(d)
126 126 self.file.flush()
127 127
128 128 d = ''.join(['%s\0%s\0' % (f, b) for f, b in backups])
129 129 self.backupsfile.write(d)
130 130 self.backupsfile.flush()
131 131
132 132 @active
133 133 def add(self, file, offset, data=None):
134 134 if file in self.map or file in self.backupmap:
135 135 return
136 136 if self._queue:
137 137 self._queue[-1][0].append((file, offset, data))
138 138 return
139 139
140 140 self.entries.append((file, offset, data))
141 141 self.map[file] = len(self.entries) - 1
142 142 # add enough data to the journal to do the truncate
143 143 self.file.write("%s\0%d\n" % (file, offset))
144 144 self.file.flush()
145 145
146 146 @active
147 147 def addbackup(self, file, hardlink=True, vfs=None):
148 148 """Adds a backup of the file to the transaction
149 149
150 150 Calling addbackup() creates a hardlink backup of the specified file
151 151 that is used to recover the file in the event of the transaction
152 152 aborting.
153 153
154 154 * `file`: the file path, relative to .hg/store
155 155 * `hardlink`: use a hardlink to quickly create the backup
156 156 """
157 157
158 158 if file in self.map or file in self.backupmap:
159 159 return
160 160 backupfile = "%s.backup.%s" % (self.journal, file)
161 161 if vfs is None:
162 162 vfs = self.opener
163 163 if vfs.exists(file):
164 164 filepath = vfs.join(file)
165 165 backuppath = self.opener.join(backupfile)
166 166 util.copyfiles(filepath, backuppath, hardlink=hardlink)
167 167 else:
168 168 self.add(file, 0)
169 169 return
170 170
171 171 if self._queue:
172 172 self._queue[-1][1].append((file, backupfile))
173 173 return
174 174
175 175 self.backupentries.append((file, backupfile, None))
176 176 self.backupmap[file] = len(self.backupentries) - 1
177 177 self.backupsfile.write("%s\0%s\0" % (file, backupfile))
178 178 self.backupsfile.flush()
179 179
180 180 @active
181 181 def addfilegenerator(self, genid, filenames, genfunc, order=0, vfs=None):
182 182 """add a function to generates some files at transaction commit
183 183
184 184 The `genfunc` argument is a function capable of generating proper
185 185 content of each entry in the `filename` tuple.
186 186
187 187 At transaction close time, `genfunc` will be called with one file
188 188 object argument per entries in `filenames`.
189 189
190 190 The transaction itself is responsible for the backup, creation and
191 191 final write of such file.
192 192
193 193 The `genid` argument is used to ensure the same set of file is only
194 194 generated once. Call to `addfilegenerator` for a `genid` already
195 195 present will overwrite the old entry.
196 196
197 197 The `order` argument may be used to control the order in which multiple
198 198 generator will be executed.
199 199 """
200 200 # For now, we are unable to do proper backup and restore of custom vfs
201 201 # but for bookmarks that are handled outside this mechanism.
202 202 assert vfs is None or filenames == ('bookmarks',)
203 203 self._filegenerators[genid] = (order, filenames, genfunc, vfs)
204 204
205 205 @active
206 206 def find(self, file):
207 207 if file in self.map:
208 208 return self.entries[self.map[file]]
209 209 if file in self.backupmap:
210 210 return self.backupentries[self.backupmap[file]]
211 211 return None
212 212
213 213 @active
214 214 def replace(self, file, offset, data=None):
215 215 '''
216 216 replace can only replace already committed entries
217 217 that are not pending in the queue
218 218 '''
219 219
220 220 if file not in self.map:
221 221 raise KeyError(file)
222 222 index = self.map[file]
223 223 self.entries[index] = (file, offset, data)
224 224 self.file.write("%s\0%d\n" % (file, offset))
225 225 self.file.flush()
226 226
227 227 @active
228 228 def nest(self):
229 229 self.count += 1
230 230 self.usages += 1
231 231 return self
232 232
233 233 def release(self):
234 234 if self.count > 0:
235 235 self.usages -= 1
236 236 # if the transaction scopes are left without being closed, fail
237 237 if self.count > 0 and self.usages == 0:
238 238 self._abort()
239 239
240 240 def running(self):
241 241 return self.count > 0
242 242
243 243 @active
244 244 def close(self):
245 245 '''commit the transaction'''
246 246 # write files registered for generation
247 247 for entry in sorted(self._filegenerators.values()):
248 248 order, filenames, genfunc, vfs = entry
249 249 if vfs is None:
250 250 vfs = self.opener
251 251 files = []
252 252 try:
253 253 for name in filenames:
254 254 # Some files are already backed up when creating the
255 255 # localrepo. Until this is properly fixed we disable the
256 256 # backup for them.
257 257 if name not in ('phaseroots', 'bookmarks'):
258 258 self.addbackup(name)
259 259 files.append(vfs(name, 'w', atomictemp=True))
260 260 genfunc(*files)
261 261 finally:
262 262 for f in files:
263 263 f.close()
264 264
265 265 if self.count == 1 and self.onclose is not None:
266 266 self.onclose()
267 267
268 268 self.count -= 1
269 269 if self.count != 0:
270 270 return
271 271 self.file.close()
272 272 self.backupsfile.close()
273 273 self.entries = []
274 274 if self.after:
275 275 self.after()
276 276 if self.opener.isfile(self.journal):
277 277 self.opener.unlink(self.journal)
278 278 if self.opener.isfile(self.backupjournal):
279 279 self.opener.unlink(self.backupjournal)
280 280 for _f, b, _ignore in self.backupentries:
281 281 self.opener.unlink(b)
282 282 self.backupentries = []
283 283 self.journal = None
284 284
285 285 @active
286 286 def abort(self):
287 287 '''abort the transaction (generally called on error, or when the
288 288 transaction is not explicitly committed before going out of
289 289 scope)'''
290 290 self._abort()
291 291
292 292 def _abort(self):
293 293 self.count = 0
294 294 self.usages = 0
295 295 self.file.close()
296 296 self.backupsfile.close()
297 297
298 298 if self.onabort is not None:
299 299 self.onabort()
300 300
301 301 try:
302 302 if not self.entries and not self.backupentries:
303 303 if self.journal:
304 304 self.opener.unlink(self.journal)
305 305 if self.backupjournal:
306 306 self.opener.unlink(self.backupjournal)
307 307 return
308 308
309 309 self.report(_("transaction abort!\n"))
310 310
311 311 try:
312 312 _playback(self.journal, self.report, self.opener,
313 313 self.entries, self.backupentries, False)
314 314 self.report(_("rollback completed\n"))
315 315 except Exception:
316 316 self.report(_("rollback failed - please run hg recover\n"))
317 317 finally:
318 318 self.journal = None
319 319
320 320
321 321 def rollback(opener, file, report):
322 322 """Rolls back the transaction contained in the given file
323 323
324 324 Reads the entries in the specified file, and the corresponding
325 325 '*.backupfiles' file, to recover from an incomplete transaction.
326 326
327 327 * `file`: a file containing a list of entries, specifying where
328 328 to truncate each file. The file should contain a list of
329 329 file\0offset pairs, delimited by newlines. The corresponding
330 330 '*.backupfiles' file should contain a list of file\0backupfile
331 331 pairs, delimited by \0.
332 332 """
333 333 entries = []
334 334 backupentries = []
335 335
336 336 fp = opener.open(file)
337 337 lines = fp.readlines()
338 338 fp.close()
339 339 for l in lines:
340 340 try:
341 341 f, o = l.split('\0')
342 342 entries.append((f, int(o), None))
343 343 except ValueError:
344 344 report(_("couldn't read journal entry %r!\n") % l)
345 345
346 346 backupjournal = "%s.backupfiles" % file
347 347 if opener.exists(backupjournal):
348 348 fp = opener.open(backupjournal)
349 349 data = fp.read()
350 350 if len(data) > 0:
351 351 parts = data.split('\0')
352 for i in xrange(0, len(parts), 2):
353 f, b = parts[i:i + 1]
352 # Skip the final part, since it's just a trailing empty space
353 for i in xrange(0, len(parts) - 1, 2):
354 f, b = parts[i:i + 2]
354 355 backupentries.append((f, b, None))
355 356
356 357 _playback(file, report, opener, entries, backupentries)
@@ -1,238 +1,281 b''
1 1 Init repo1:
2 2
3 3 $ hg init repo1
4 4 $ cd repo1
5 5 $ echo "some text" > a
6 6 $ hg add
7 7 adding a
8 8 $ hg ci -m first
9 9 $ cat .hg/store/fncache | sort
10 10 data/a.i
11 11
12 12 Testing a.i/b:
13 13
14 14 $ mkdir a.i
15 15 $ echo "some other text" > a.i/b
16 16 $ hg add
17 17 adding a.i/b (glob)
18 18 $ hg ci -m second
19 19 $ cat .hg/store/fncache | sort
20 20 data/a.i
21 21 data/a.i.hg/b.i
22 22
23 23 Testing a.i.hg/c:
24 24
25 25 $ mkdir a.i.hg
26 26 $ echo "yet another text" > a.i.hg/c
27 27 $ hg add
28 28 adding a.i.hg/c (glob)
29 29 $ hg ci -m third
30 30 $ cat .hg/store/fncache | sort
31 31 data/a.i
32 32 data/a.i.hg.hg/c.i
33 33 data/a.i.hg/b.i
34 34
35 35 Testing verify:
36 36
37 37 $ hg verify
38 38 checking changesets
39 39 checking manifests
40 40 crosschecking files in changesets and manifests
41 41 checking files
42 42 3 files, 3 changesets, 3 total revisions
43 43
44 44 $ rm .hg/store/fncache
45 45
46 46 $ hg verify
47 47 checking changesets
48 48 checking manifests
49 49 crosschecking files in changesets and manifests
50 50 checking files
51 51 data/a.i@0: missing revlog!
52 52 data/a.i.hg/c.i@2: missing revlog!
53 53 data/a.i/b.i@1: missing revlog!
54 54 3 files, 3 changesets, 3 total revisions
55 55 3 integrity errors encountered!
56 56 (first damaged changeset appears to be 0)
57 57 [1]
58 58 $ cd ..
59 59
60 60 Non store repo:
61 61
62 62 $ hg --config format.usestore=False init foo
63 63 $ cd foo
64 64 $ mkdir tst.d
65 65 $ echo foo > tst.d/foo
66 66 $ hg ci -Amfoo
67 67 adding tst.d/foo
68 68 $ find .hg | sort
69 69 .hg
70 70 .hg/00changelog.i
71 71 .hg/00manifest.i
72 72 .hg/cache
73 73 .hg/cache/branch2-served
74 74 .hg/data
75 75 .hg/data/tst.d.hg
76 76 .hg/data/tst.d.hg/foo.i
77 77 .hg/dirstate
78 78 .hg/last-message.txt
79 79 .hg/phaseroots
80 80 .hg/requires
81 81 .hg/undo
82 82 .hg/undo.bookmarks
83 83 .hg/undo.branch
84 84 .hg/undo.desc
85 85 .hg/undo.dirstate
86 86 .hg/undo.phaseroots
87 87 $ cd ..
88 88
89 89 Non fncache repo:
90 90
91 91 $ hg --config format.usefncache=False init bar
92 92 $ cd bar
93 93 $ mkdir tst.d
94 94 $ echo foo > tst.d/Foo
95 95 $ hg ci -Amfoo
96 96 adding tst.d/Foo
97 97 $ find .hg | sort
98 98 .hg
99 99 .hg/00changelog.i
100 100 .hg/cache
101 101 .hg/cache/branch2-served
102 102 .hg/dirstate
103 103 .hg/last-message.txt
104 104 .hg/requires
105 105 .hg/store
106 106 .hg/store/00changelog.i
107 107 .hg/store/00manifest.i
108 108 .hg/store/data
109 109 .hg/store/data/tst.d.hg
110 110 .hg/store/data/tst.d.hg/_foo.i
111 111 .hg/store/phaseroots
112 112 .hg/store/undo
113 113 .hg/store/undo.phaseroots
114 114 .hg/undo.bookmarks
115 115 .hg/undo.branch
116 116 .hg/undo.desc
117 117 .hg/undo.dirstate
118 118 $ cd ..
119 119
120 120 Encoding of reserved / long paths in the store
121 121
122 122 $ hg init r2
123 123 $ cd r2
124 124 $ cat <<EOF > .hg/hgrc
125 125 > [ui]
126 126 > portablefilenames = ignore
127 127 > EOF
128 128
129 129 $ hg import -q --bypass - <<EOF
130 130 > # HG changeset patch
131 131 > # User test
132 132 > # Date 0 0
133 133 > # Node ID 1c7a2f7cb77be1a0def34e4c7cabc562ad98fbd7
134 134 > # Parent 0000000000000000000000000000000000000000
135 135 > 1
136 136 >
137 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 138 > new file mode 100644
139 139 > --- /dev/null
140 140 > +++ b/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz
141 141 > @@ -0,0 +1,1 @@
142 142 > +foo
143 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 144 > new file mode 100644
145 145 > --- /dev/null
146 146 > +++ b/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT
147 147 > @@ -0,0 +1,1 @@
148 148 > +foo
149 149 > diff --git a/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
150 150 > new file mode 100644
151 151 > --- /dev/null
152 152 > +++ b/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt
153 153 > @@ -0,0 +1,1 @@
154 154 > +foo
155 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 156 > new file mode 100644
157 157 > --- /dev/null
158 158 > +++ b/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c
159 159 > @@ -0,0 +1,1 @@
160 160 > +foo
161 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 162 > new file mode 100644
163 163 > --- /dev/null
164 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 165 > @@ -0,0 +1,1 @@
166 166 > +foo
167 167 > EOF
168 168
169 169 $ find .hg/store -name *.i | sort
170 170 .hg/store/00changelog.i
171 171 .hg/store/00manifest.i
172 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 173 .hg/store/dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxx168e07b38e65eff86ab579afaaa8e30bfbe0f35f.i
174 174 .hg/store/dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i
175 175 .hg/store/dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i
176 176 .hg/store/dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilename0d8e1f4187c650e2f1fdca9fd90f786bc0976b6b.i
177 177
178 178 $ cd ..
179 179
180 180 Aborting lock does not prevent fncache writes
181 181
182 182 $ cat > exceptionext.py <<EOF
183 183 > import os
184 184 > from mercurial import commands, util
185 185 > from mercurial.extensions import wrapfunction
186 186 >
187 187 > def lockexception(orig, vfs, lockname, wait, releasefn, acquirefn, desc):
188 188 > def releasewrap():
189 189 > raise util.Abort("forced lock failure")
190 190 > return orig(vfs, lockname, wait, releasewrap, acquirefn, desc)
191 191 >
192 192 > def reposetup(ui, repo):
193 193 > wrapfunction(repo, '_lock', lockexception)
194 194 >
195 195 > cmdtable = {}
196 196 >
197 197 > EOF
198 198 $ extpath=`pwd`/exceptionext.py
199 199 $ hg init fncachetxn
200 200 $ cd fncachetxn
201 201 $ printf "[extensions]\nexceptionext=$extpath\n" >> .hg/hgrc
202 202 $ touch y
203 203 $ hg ci -qAm y
204 204 abort: forced lock failure
205 205 [255]
206 206 $ cat .hg/store/fncache
207 207 data/y.i
208 208
209 209 Aborting transaction prevents fncache change
210 210
211 211 $ cat > ../exceptionext.py <<EOF
212 212 > import os
213 213 > from mercurial import commands, util, transaction
214 214 > from mercurial.extensions import wrapfunction
215 215 >
216 216 > def wrapper(orig, self, *args, **kwargs):
217 217 > origonclose = self.onclose
218 218 > def onclose():
219 219 > origonclose()
220 220 > raise util.Abort("forced transaction failure")
221 221 > self.onclose = onclose
222 222 > return orig(self, *args, **kwargs)
223 223 >
224 224 > def uisetup(ui):
225 225 > wrapfunction(transaction.transaction, 'close', wrapper)
226 226 >
227 227 > cmdtable = {}
228 228 >
229 229 > EOF
230 230 $ rm -f "${extpath}c"
231 231 $ touch z
232 232 $ hg ci -qAm z
233 233 transaction abort!
234 234 rollback completed
235 235 abort: forced transaction failure
236 236 [255]
237 237 $ cat .hg/store/fncache
238 238 data/y.i
239
240 Aborted transactions can be recovered later
241
242 $ cat > ../exceptionext.py <<EOF
243 > import os
244 > from mercurial import commands, util, transaction
245 > from mercurial.extensions import wrapfunction
246 >
247 > def closewrapper(orig, self, *args, **kwargs):
248 > origonclose = self.onclose
249 > def onclose():
250 > origonclose()
251 > raise util.Abort("forced transaction failure")
252 > self.onclose = onclose
253 > return orig(self, *args, **kwargs)
254 >
255 > def abortwrapper(orig, self, *args, **kwargs):
256 > raise util.Abort("forced transaction failure")
257 >
258 > def uisetup(ui):
259 > wrapfunction(transaction.transaction, 'close', closewrapper)
260 > wrapfunction(transaction.transaction, '_abort', abortwrapper)
261 >
262 > cmdtable = {}
263 >
264 > EOF
265 $ rm -f "${extpath}c"
266 $ hg up -q 1
267 $ touch z
268 $ hg ci -qAm z 2>/dev/null
269 [255]
270 $ cat .hg/store/fncache | sort
271 data/y.i
272 data/z.i
273 $ hg recover
274 rolling back interrupted transaction
275 checking changesets
276 checking manifests
277 crosschecking files in changesets and manifests
278 checking files
279 1 files, 1 changesets, 1 total revisions
280 $ cat .hg/store/fncache
281 data/y.i
General Comments 0
You need to be logged in to leave comments. Login now