##// 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 # 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 errno
15 import 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, backupentries, 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:
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 opener.unlink(f)
38 opener.unlink(f)
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
42
43 backupfiles = []
43 backupfiles = []
44 for f, b, _ignore in backupentries:
44 for f, b, _ignore in backupentries:
45 filepath = opener.join(f)
45 filepath = opener.join(f)
46 backuppath = opener.join(b)
46 backuppath = opener.join(b)
47 try:
47 try:
48 util.copyfile(backuppath, filepath)
48 util.copyfile(backuppath, filepath)
49 backupfiles.append(b)
49 backupfiles.append(b)
50 except IOError:
50 except IOError:
51 report(_("failed to recover %s\n") % f)
51 report(_("failed to recover %s\n") % f)
52 raise
52 raise
53
53
54 opener.unlink(journal)
54 opener.unlink(journal)
55 backuppath = "%s.backupfiles" % journal
55 backuppath = "%s.backupfiles" % journal
56 if opener.exists(backuppath):
56 if opener.exists(backuppath):
57 opener.unlink(backuppath)
57 opener.unlink(backuppath)
58 for f in backupfiles:
58 for f in backupfiles:
59 opener.unlink(f)
59 opener.unlink(f)
60
60
61 class transaction(object):
61 class transaction(object):
62 def __init__(self, report, opener, journal, after=None, createmode=None,
62 def __init__(self, report, opener, journal, after=None, createmode=None,
63 onclose=None, onabort=None):
63 onclose=None, onabort=None):
64 """Begin a new transaction
64 """Begin a new transaction
65
65
66 Begins a new transaction that allows rolling back writes in the event of
66 Begins a new transaction that allows rolling back writes in the event of
67 an exception.
67 an exception.
68
68
69 * `after`: called after the transaction has been committed
69 * `after`: called after the transaction has been committed
70 * `createmode`: the mode of the journal file that will be created
70 * `createmode`: the mode of the journal file that will be created
71 * `onclose`: called as the transaction is closing, but before it is
71 * `onclose`: called as the transaction is closing, but before it is
72 closed
72 closed
73 * `onabort`: called as the transaction is aborting, but before any files
73 * `onabort`: called as the transaction is aborting, but before any files
74 have been truncated
74 have been truncated
75 """
75 """
76 self.count = 1
76 self.count = 1
77 self.usages = 1
77 self.usages = 1
78 self.report = report
78 self.report = report
79 self.opener = opener
79 self.opener = opener
80 self.after = after
80 self.after = after
81 self.onclose = onclose
81 self.onclose = onclose
82 self.onabort = onabort
82 self.onabort = onabort
83 self.entries = []
83 self.entries = []
84 self.backupentries = []
84 self.backupentries = []
85 self.map = {}
85 self.map = {}
86 self.backupmap = {}
86 self.backupmap = {}
87 self.journal = journal
87 self.journal = journal
88 self._queue = []
88 self._queue = []
89 # a dict of arguments to be passed to hooks
89 # a dict of arguments to be passed to hooks
90 self.hookargs = {}
90 self.hookargs = {}
91
91
92 self.backupjournal = "%s.backupfiles" % journal
92 self.backupjournal = "%s.backupfiles" % journal
93 self.file = opener.open(self.journal, "w")
93 self.file = opener.open(self.journal, "w")
94 self.backupsfile = opener.open(self.backupjournal, 'w')
94 self.backupsfile = opener.open(self.backupjournal, 'w')
95 if createmode is not None:
95 if createmode is not None:
96 opener.chmod(self.journal, createmode & 0666)
96 opener.chmod(self.journal, createmode & 0666)
97 opener.chmod(self.backupjournal, createmode & 0666)
97 opener.chmod(self.backupjournal, createmode & 0666)
98
98
99 # hold file generations to be performed on commit
99 # hold file generations to be performed on commit
100 self._filegenerators = {}
100 self._filegenerators = {}
101
101
102 def __del__(self):
102 def __del__(self):
103 if self.journal:
103 if self.journal:
104 self._abort()
104 self._abort()
105
105
106 @active
106 @active
107 def startgroup(self):
107 def startgroup(self):
108 self._queue.append(([], []))
108 self._queue.append(([], []))
109
109
110 @active
110 @active
111 def endgroup(self):
111 def endgroup(self):
112 q = self._queue.pop()
112 q = self._queue.pop()
113 self.entries.extend(q[0])
113 self.entries.extend(q[0])
114 self.backupentries.extend(q[1])
114 self.backupentries.extend(q[1])
115
115
116 offsets = []
116 offsets = []
117 backups = []
117 backups = []
118 for f, o, _data in q[0]:
118 for f, o, _data in q[0]:
119 offsets.append((f, o))
119 offsets.append((f, o))
120
120
121 for f, b, _data in q[1]:
121 for f, b, _data in q[1]:
122 backups.append((f, b))
122 backups.append((f, b))
123
123
124 d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
124 d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
125 self.file.write(d)
125 self.file.write(d)
126 self.file.flush()
126 self.file.flush()
127
127
128 d = ''.join(['%s\0%s\0' % (f, b) for f, b in backups])
128 d = ''.join(['%s\0%s\0' % (f, b) for f, b in backups])
129 self.backupsfile.write(d)
129 self.backupsfile.write(d)
130 self.backupsfile.flush()
130 self.backupsfile.flush()
131
131
132 @active
132 @active
133 def add(self, file, offset, data=None):
133 def add(self, file, offset, data=None):
134 if file in self.map or file in self.backupmap:
134 if file in self.map or file in self.backupmap:
135 return
135 return
136 if self._queue:
136 if self._queue:
137 self._queue[-1][0].append((file, offset, data))
137 self._queue[-1][0].append((file, offset, data))
138 return
138 return
139
139
140 self.entries.append((file, offset, data))
140 self.entries.append((file, offset, data))
141 self.map[file] = len(self.entries) - 1
141 self.map[file] = len(self.entries) - 1
142 # add enough data to the journal to do the truncate
142 # add enough data to the journal to do the truncate
143 self.file.write("%s\0%d\n" % (file, offset))
143 self.file.write("%s\0%d\n" % (file, offset))
144 self.file.flush()
144 self.file.flush()
145
145
146 @active
146 @active
147 def addbackup(self, file, hardlink=True, vfs=None):
147 def addbackup(self, file, hardlink=True, vfs=None):
148 """Adds a backup of the file to the transaction
148 """Adds a backup of the file to the transaction
149
149
150 Calling addbackup() creates a hardlink backup of the specified file
150 Calling addbackup() creates a hardlink backup of the specified file
151 that is used to recover the file in the event of the transaction
151 that is used to recover the file in the event of the transaction
152 aborting.
152 aborting.
153
153
154 * `file`: the file path, relative to .hg/store
154 * `file`: the file path, relative to .hg/store
155 * `hardlink`: use a hardlink to quickly create the backup
155 * `hardlink`: use a hardlink to quickly create the backup
156 """
156 """
157
157
158 if file in self.map or file in self.backupmap:
158 if file in self.map or file in self.backupmap:
159 return
159 return
160 backupfile = "%s.backup.%s" % (self.journal, file)
160 backupfile = "%s.backup.%s" % (self.journal, file)
161 if vfs is None:
161 if vfs is None:
162 vfs = self.opener
162 vfs = self.opener
163 if vfs.exists(file):
163 if vfs.exists(file):
164 filepath = vfs.join(file)
164 filepath = vfs.join(file)
165 backuppath = self.opener.join(backupfile)
165 backuppath = self.opener.join(backupfile)
166 util.copyfiles(filepath, backuppath, hardlink=hardlink)
166 util.copyfiles(filepath, backuppath, hardlink=hardlink)
167 else:
167 else:
168 self.add(file, 0)
168 self.add(file, 0)
169 return
169 return
170
170
171 if self._queue:
171 if self._queue:
172 self._queue[-1][1].append((file, backupfile))
172 self._queue[-1][1].append((file, backupfile))
173 return
173 return
174
174
175 self.backupentries.append((file, backupfile, None))
175 self.backupentries.append((file, backupfile, None))
176 self.backupmap[file] = len(self.backupentries) - 1
176 self.backupmap[file] = len(self.backupentries) - 1
177 self.backupsfile.write("%s\0%s\0" % (file, backupfile))
177 self.backupsfile.write("%s\0%s\0" % (file, backupfile))
178 self.backupsfile.flush()
178 self.backupsfile.flush()
179
179
180 @active
180 @active
181 def addfilegenerator(self, genid, filenames, genfunc, order=0, vfs=None):
181 def addfilegenerator(self, genid, filenames, genfunc, order=0, vfs=None):
182 """add a function to generates some files at transaction commit
182 """add a function to generates some files at transaction commit
183
183
184 The `genfunc` argument is a function capable of generating proper
184 The `genfunc` argument is a function capable of generating proper
185 content of each entry in the `filename` tuple.
185 content of each entry in the `filename` tuple.
186
186
187 At transaction close time, `genfunc` will be called with one file
187 At transaction close time, `genfunc` will be called with one file
188 object argument per entries in `filenames`.
188 object argument per entries in `filenames`.
189
189
190 The transaction itself is responsible for the backup, creation and
190 The transaction itself is responsible for the backup, creation and
191 final write of such file.
191 final write of such file.
192
192
193 The `genid` argument is used to ensure the same set of file is only
193 The `genid` argument is used to ensure the same set of file is only
194 generated once. Call to `addfilegenerator` for a `genid` already
194 generated once. Call to `addfilegenerator` for a `genid` already
195 present will overwrite the old entry.
195 present will overwrite the old entry.
196
196
197 The `order` argument may be used to control the order in which multiple
197 The `order` argument may be used to control the order in which multiple
198 generator will be executed.
198 generator will be executed.
199 """
199 """
200 # For now, we are unable to do proper backup and restore of custom vfs
200 # For now, we are unable to do proper backup and restore of custom vfs
201 # but for bookmarks that are handled outside this mechanism.
201 # but for bookmarks that are handled outside this mechanism.
202 assert vfs is None or filenames == ('bookmarks',)
202 assert vfs is None or filenames == ('bookmarks',)
203 self._filegenerators[genid] = (order, filenames, genfunc, vfs)
203 self._filegenerators[genid] = (order, filenames, genfunc, vfs)
204
204
205 @active
205 @active
206 def find(self, file):
206 def find(self, file):
207 if file in self.map:
207 if file in self.map:
208 return self.entries[self.map[file]]
208 return self.entries[self.map[file]]
209 if file in self.backupmap:
209 if file in self.backupmap:
210 return self.backupentries[self.backupmap[file]]
210 return self.backupentries[self.backupmap[file]]
211 return None
211 return None
212
212
213 @active
213 @active
214 def replace(self, file, offset, data=None):
214 def replace(self, file, offset, data=None):
215 '''
215 '''
216 replace can only replace already committed entries
216 replace can only replace already committed entries
217 that are not pending in the queue
217 that are not pending in the queue
218 '''
218 '''
219
219
220 if file not in self.map:
220 if file not in self.map:
221 raise KeyError(file)
221 raise KeyError(file)
222 index = self.map[file]
222 index = self.map[file]
223 self.entries[index] = (file, offset, data)
223 self.entries[index] = (file, offset, data)
224 self.file.write("%s\0%d\n" % (file, offset))
224 self.file.write("%s\0%d\n" % (file, offset))
225 self.file.flush()
225 self.file.flush()
226
226
227 @active
227 @active
228 def nest(self):
228 def nest(self):
229 self.count += 1
229 self.count += 1
230 self.usages += 1
230 self.usages += 1
231 return self
231 return self
232
232
233 def release(self):
233 def release(self):
234 if self.count > 0:
234 if self.count > 0:
235 self.usages -= 1
235 self.usages -= 1
236 # if the transaction scopes are left without being closed, fail
236 # if the transaction scopes are left without being closed, fail
237 if self.count > 0 and self.usages == 0:
237 if self.count > 0 and self.usages == 0:
238 self._abort()
238 self._abort()
239
239
240 def running(self):
240 def running(self):
241 return self.count > 0
241 return self.count > 0
242
242
243 @active
243 @active
244 def close(self):
244 def close(self):
245 '''commit the transaction'''
245 '''commit the transaction'''
246 # write files registered for generation
246 # write files registered for generation
247 for entry in sorted(self._filegenerators.values()):
247 for entry in sorted(self._filegenerators.values()):
248 order, filenames, genfunc, vfs = entry
248 order, filenames, genfunc, vfs = entry
249 if vfs is None:
249 if vfs is None:
250 vfs = self.opener
250 vfs = self.opener
251 files = []
251 files = []
252 try:
252 try:
253 for name in filenames:
253 for name in filenames:
254 # Some files are already backed up when creating the
254 # Some files are already backed up when creating the
255 # localrepo. Until this is properly fixed we disable the
255 # localrepo. Until this is properly fixed we disable the
256 # backup for them.
256 # backup for them.
257 if name not in ('phaseroots', 'bookmarks'):
257 if name not in ('phaseroots', 'bookmarks'):
258 self.addbackup(name)
258 self.addbackup(name)
259 files.append(vfs(name, 'w', atomictemp=True))
259 files.append(vfs(name, 'w', atomictemp=True))
260 genfunc(*files)
260 genfunc(*files)
261 finally:
261 finally:
262 for f in files:
262 for f in files:
263 f.close()
263 f.close()
264
264
265 if self.count == 1 and self.onclose is not None:
265 if self.count == 1 and self.onclose is not None:
266 self.onclose()
266 self.onclose()
267
267
268 self.count -= 1
268 self.count -= 1
269 if self.count != 0:
269 if self.count != 0:
270 return
270 return
271 self.file.close()
271 self.file.close()
272 self.backupsfile.close()
272 self.backupsfile.close()
273 self.entries = []
273 self.entries = []
274 if self.after:
274 if self.after:
275 self.after()
275 self.after()
276 if self.opener.isfile(self.journal):
276 if self.opener.isfile(self.journal):
277 self.opener.unlink(self.journal)
277 self.opener.unlink(self.journal)
278 if self.opener.isfile(self.backupjournal):
278 if self.opener.isfile(self.backupjournal):
279 self.opener.unlink(self.backupjournal)
279 self.opener.unlink(self.backupjournal)
280 for _f, b, _ignore in self.backupentries:
280 for _f, b, _ignore in self.backupentries:
281 self.opener.unlink(b)
281 self.opener.unlink(b)
282 self.backupentries = []
282 self.backupentries = []
283 self.journal = None
283 self.journal = None
284
284
285 @active
285 @active
286 def abort(self):
286 def abort(self):
287 '''abort the transaction (generally called on error, or when the
287 '''abort the transaction (generally called on error, or when the
288 transaction is not explicitly committed before going out of
288 transaction is not explicitly committed before going out of
289 scope)'''
289 scope)'''
290 self._abort()
290 self._abort()
291
291
292 def _abort(self):
292 def _abort(self):
293 self.count = 0
293 self.count = 0
294 self.usages = 0
294 self.usages = 0
295 self.file.close()
295 self.file.close()
296 self.backupsfile.close()
296 self.backupsfile.close()
297
297
298 if self.onabort is not None:
298 if self.onabort is not None:
299 self.onabort()
299 self.onabort()
300
300
301 try:
301 try:
302 if not self.entries and not self.backupentries:
302 if not self.entries and not self.backupentries:
303 if self.journal:
303 if self.journal:
304 self.opener.unlink(self.journal)
304 self.opener.unlink(self.journal)
305 if self.backupjournal:
305 if self.backupjournal:
306 self.opener.unlink(self.backupjournal)
306 self.opener.unlink(self.backupjournal)
307 return
307 return
308
308
309 self.report(_("transaction abort!\n"))
309 self.report(_("transaction abort!\n"))
310
310
311 try:
311 try:
312 _playback(self.journal, self.report, self.opener,
312 _playback(self.journal, self.report, self.opener,
313 self.entries, self.backupentries, False)
313 self.entries, self.backupentries, False)
314 self.report(_("rollback completed\n"))
314 self.report(_("rollback completed\n"))
315 except Exception:
315 except Exception:
316 self.report(_("rollback failed - please run hg recover\n"))
316 self.report(_("rollback failed - please run hg recover\n"))
317 finally:
317 finally:
318 self.journal = None
318 self.journal = None
319
319
320
320
321 def rollback(opener, file, report):
321 def rollback(opener, file, report):
322 """Rolls back the transaction contained in the given file
322 """Rolls back the transaction contained in the given file
323
323
324 Reads the entries in the specified file, and the corresponding
324 Reads the entries in the specified file, and the corresponding
325 '*.backupfiles' file, to recover from an incomplete transaction.
325 '*.backupfiles' file, to recover from an incomplete transaction.
326
326
327 * `file`: a file containing a list of entries, specifying where
327 * `file`: a file containing a list of entries, specifying where
328 to truncate each file. The file should contain a list of
328 to truncate each file. The file should contain a list of
329 file\0offset pairs, delimited by newlines. The corresponding
329 file\0offset pairs, delimited by newlines. The corresponding
330 '*.backupfiles' file should contain a list of file\0backupfile
330 '*.backupfiles' file should contain a list of file\0backupfile
331 pairs, delimited by \0.
331 pairs, delimited by \0.
332 """
332 """
333 entries = []
333 entries = []
334 backupentries = []
334 backupentries = []
335
335
336 fp = opener.open(file)
336 fp = opener.open(file)
337 lines = fp.readlines()
337 lines = fp.readlines()
338 fp.close()
338 fp.close()
339 for l in lines:
339 for l in lines:
340 try:
340 try:
341 f, o = l.split('\0')
341 f, o = l.split('\0')
342 entries.append((f, int(o), None))
342 entries.append((f, int(o), None))
343 except ValueError:
343 except ValueError:
344 report(_("couldn't read journal entry %r!\n") % l)
344 report(_("couldn't read journal entry %r!\n") % l)
345
345
346 backupjournal = "%s.backupfiles" % file
346 backupjournal = "%s.backupfiles" % file
347 if opener.exists(backupjournal):
347 if opener.exists(backupjournal):
348 fp = opener.open(backupjournal)
348 fp = opener.open(backupjournal)
349 data = fp.read()
349 data = fp.read()
350 if len(data) > 0:
350 if len(data) > 0:
351 parts = data.split('\0')
351 parts = data.split('\0')
352 for i in xrange(0, len(parts), 2):
352 # Skip the final part, since it's just a trailing empty space
353 f, b = parts[i:i + 1]
353 for i in xrange(0, len(parts) - 1, 2):
354 f, b = parts[i:i + 2]
354 backupentries.append((f, b, None))
355 backupentries.append((f, b, None))
355
356
356 _playback(file, report, opener, entries, backupentries)
357 _playback(file, report, opener, entries, backupentries)
@@ -1,238 +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, transaction
213 > from mercurial import commands, util, transaction
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 > origonclose = self.onclose
217 > origonclose = self.onclose
218 > def onclose():
218 > def onclose():
219 > origonclose()
219 > origonclose()
220 > raise util.Abort("forced transaction failure")
220 > raise util.Abort("forced transaction failure")
221 > self.onclose = onclose
221 > self.onclose = onclose
222 > return orig(self, *args, **kwargs)
222 > return orig(self, *args, **kwargs)
223 >
223 >
224 > def uisetup(ui):
224 > def uisetup(ui):
225 > wrapfunction(transaction.transaction, 'close', wrapper)
225 > wrapfunction(transaction.transaction, 'close', 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
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