##// END OF EJS Templates
transaction: only delete journal on successful abort/commit...
Henrik Stuart -
r8071:9f14b668 default
parent child Browse files
Show More
@@ -1,112 +1,118 b''
1 # transaction.py - simple journalling scheme for mercurial
1 # transaction.py - simple journalling scheme for mercurial
2 #
2 #
3 # This transaction scheme is intended to gracefully handle program
3 # This transaction scheme is intended to gracefully handle program
4 # errors and interruptions. More serious failures like system crashes
4 # errors and interruptions. More serious failures like system crashes
5 # can be recovered with an fsck-like tool. As the whole repository is
5 # can be recovered with an fsck-like tool. As the whole repository is
6 # effectively log-structured, this should amount to simply truncating
6 # effectively log-structured, this should amount to simply truncating
7 # anything that isn't referenced in the changelog.
7 # anything that isn't referenced in the changelog.
8 #
8 #
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 #
10 #
11 # This software may be used and distributed according to the terms
11 # This software may be used and distributed according to the terms
12 # of the GNU General Public License, incorporated herein by reference.
12 # of the GNU General Public License, incorporated herein by reference.
13
13
14 from i18n import _
14 from i18n import _
15 import os, errno
15 import os, errno
16
16
17 class transaction(object):
17 class transaction(object):
18 def __init__(self, report, opener, journal, after=None, createmode=None):
18 def __init__(self, report, opener, journal, after=None, createmode=None):
19 self.journal = None
19 self.journal = None
20
20
21 self.count = 1
21 self.count = 1
22 self.report = report
22 self.report = report
23 self.opener = opener
23 self.opener = opener
24 self.after = after
24 self.after = after
25 self.entries = []
25 self.entries = []
26 self.map = {}
26 self.map = {}
27 self.journal = journal
27 self.journal = journal
28
28
29 self.file = open(self.journal, "w")
29 self.file = open(self.journal, "w")
30 if createmode is not None:
30 if createmode is not None:
31 os.chmod(self.journal, createmode & 0666)
31 os.chmod(self.journal, createmode & 0666)
32
32
33 def __del__(self):
33 def __del__(self):
34 if self.journal:
34 if self.journal:
35 if self.entries: self.abort()
35 if self.entries: self.abort()
36 self.file.close()
36 self.file.close()
37 try: os.unlink(self.journal)
38 except: pass
39
37
40 def add(self, file, offset, data=None):
38 def add(self, file, offset, data=None):
41 if file in self.map: return
39 if file in self.map: return
42 self.entries.append((file, offset, data))
40 self.entries.append((file, offset, data))
43 self.map[file] = len(self.entries) - 1
41 self.map[file] = len(self.entries) - 1
44 # add enough data to the journal to do the truncate
42 # add enough data to the journal to do the truncate
45 self.file.write("%s\0%d\n" % (file, offset))
43 self.file.write("%s\0%d\n" % (file, offset))
46 self.file.flush()
44 self.file.flush()
47
45
48 def find(self, file):
46 def find(self, file):
49 if file in self.map:
47 if file in self.map:
50 return self.entries[self.map[file]]
48 return self.entries[self.map[file]]
51 return None
49 return None
52
50
53 def replace(self, file, offset, data=None):
51 def replace(self, file, offset, data=None):
54 if file not in self.map:
52 if file not in self.map:
55 raise KeyError(file)
53 raise KeyError(file)
56 index = self.map[file]
54 index = self.map[file]
57 self.entries[index] = (file, offset, data)
55 self.entries[index] = (file, offset, data)
58 self.file.write("%s\0%d\n" % (file, offset))
56 self.file.write("%s\0%d\n" % (file, offset))
59 self.file.flush()
57 self.file.flush()
60
58
61 def nest(self):
59 def nest(self):
62 self.count += 1
60 self.count += 1
63 return self
61 return self
64
62
65 def running(self):
63 def running(self):
66 return self.count > 0
64 return self.count > 0
67
65
68 def close(self):
66 def close(self):
69 self.count -= 1
67 self.count -= 1
70 if self.count != 0:
68 if self.count != 0:
71 return
69 return
72 self.file.close()
70 self.file.close()
73 self.entries = []
71 self.entries = []
74 if self.after:
72 if self.after:
75 self.after()
73 self.after()
76 else:
74 else:
77 os.unlink(self.journal)
75 os.unlink(self.journal)
78 self.journal = None
76 self.journal = None
79
77
80 def abort(self):
78 def abort(self):
81 if not self.entries: return
79 if not self.entries: return
82
80
83 self.report(_("transaction abort!\n"))
81 self.report(_("transaction abort!\n"))
84
82
83 failed = False
85 for f, o, ignore in self.entries:
84 for f, o, ignore in self.entries:
86 try:
85 try:
87 self.opener(f, "a").truncate(o)
86 self.opener(f, "a").truncate(o)
88 except:
87 except:
88 failed = True
89 self.report(_("failed to truncate %s\n") % f)
89 self.report(_("failed to truncate %s\n") % f)
90
90
91 self.entries = []
91 self.entries = []
92
92
93 if not failed:
94 self.file.close()
95 os.unlink(self.journal)
96 self.journal = None
93 self.report(_("rollback completed\n"))
97 self.report(_("rollback completed\n"))
98 else:
99 self.report(_("rollback failed - please run hg recover\n"))
94
100
95 def rollback(opener, file):
101 def rollback(opener, file):
96 files = {}
102 files = {}
97 for l in open(file).readlines():
103 for l in open(file).readlines():
98 f, o = l.split('\0')
104 f, o = l.split('\0')
99 files[f] = int(o)
105 files[f] = int(o)
100 for f in files:
106 for f in files:
101 o = files[f]
107 o = files[f]
102 if o:
108 if o:
103 opener(f, "a").truncate(int(o))
109 opener(f, "a").truncate(int(o))
104 else:
110 else:
105 try:
111 try:
106 fn = opener(f).name
112 fn = opener(f).name
107 os.unlink(fn)
113 os.unlink(fn)
108 except OSError, inst:
114 except OSError, inst:
109 if inst.errno != errno.ENOENT:
115 if inst.errno != errno.ENOENT:
110 raise
116 raise
111 os.unlink(file)
117 os.unlink(file)
112
118
General Comments 0
You need to be logged in to leave comments. Login now