##// END OF EJS Templates
changelog: fixes leaked file handle
Zachary Gramana -
r14207:c1cca388 default
parent child Browse files
Show More
@@ -1,233 +1,235 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import bin, hex, nullid
8 from node import bin, hex, nullid
9 from i18n import _
9 from i18n import _
10 import util, error, revlog, encoding
10 import util, error, revlog, encoding
11
11
12 def _string_escape(text):
12 def _string_escape(text):
13 """
13 """
14 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
14 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
15 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
15 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
16 >>> s
16 >>> s
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
18 >>> res = _string_escape(s)
18 >>> res = _string_escape(s)
19 >>> s == res.decode('string_escape')
19 >>> s == res.decode('string_escape')
20 True
20 True
21 """
21 """
22 # subset of the string_escape codec
22 # subset of the string_escape codec
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
24 return text.replace('\0', '\\0')
24 return text.replace('\0', '\\0')
25
25
26 def decodeextra(text):
26 def decodeextra(text):
27 extra = {}
27 extra = {}
28 for l in text.split('\0'):
28 for l in text.split('\0'):
29 if l:
29 if l:
30 k, v = l.decode('string_escape').split(':', 1)
30 k, v = l.decode('string_escape').split(':', 1)
31 extra[k] = v
31 extra[k] = v
32 return extra
32 return extra
33
33
34 def encodeextra(d):
34 def encodeextra(d):
35 # keys must be sorted to produce a deterministic changelog entry
35 # keys must be sorted to produce a deterministic changelog entry
36 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
36 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
37 return "\0".join(items)
37 return "\0".join(items)
38
38
39 class appender(object):
39 class appender(object):
40 '''the changelog index must be updated last on disk, so we use this class
40 '''the changelog index must be updated last on disk, so we use this class
41 to delay writes to it'''
41 to delay writes to it'''
42 def __init__(self, fp, buf):
42 def __init__(self, fp, buf):
43 self.data = buf
43 self.data = buf
44 self.fp = fp
44 self.fp = fp
45 self.offset = fp.tell()
45 self.offset = fp.tell()
46 self.size = util.fstat(fp).st_size
46 self.size = util.fstat(fp).st_size
47
47
48 def end(self):
48 def end(self):
49 return self.size + len("".join(self.data))
49 return self.size + len("".join(self.data))
50 def tell(self):
50 def tell(self):
51 return self.offset
51 return self.offset
52 def flush(self):
52 def flush(self):
53 pass
53 pass
54 def close(self):
54 def close(self):
55 self.fp.close()
55 self.fp.close()
56
56
57 def seek(self, offset, whence=0):
57 def seek(self, offset, whence=0):
58 '''virtual file offset spans real file and data'''
58 '''virtual file offset spans real file and data'''
59 if whence == 0:
59 if whence == 0:
60 self.offset = offset
60 self.offset = offset
61 elif whence == 1:
61 elif whence == 1:
62 self.offset += offset
62 self.offset += offset
63 elif whence == 2:
63 elif whence == 2:
64 self.offset = self.end() + offset
64 self.offset = self.end() + offset
65 if self.offset < self.size:
65 if self.offset < self.size:
66 self.fp.seek(self.offset)
66 self.fp.seek(self.offset)
67
67
68 def read(self, count=-1):
68 def read(self, count=-1):
69 '''only trick here is reads that span real file and data'''
69 '''only trick here is reads that span real file and data'''
70 ret = ""
70 ret = ""
71 if self.offset < self.size:
71 if self.offset < self.size:
72 s = self.fp.read(count)
72 s = self.fp.read(count)
73 ret = s
73 ret = s
74 self.offset += len(s)
74 self.offset += len(s)
75 if count > 0:
75 if count > 0:
76 count -= len(s)
76 count -= len(s)
77 if count != 0:
77 if count != 0:
78 doff = self.offset - self.size
78 doff = self.offset - self.size
79 self.data.insert(0, "".join(self.data))
79 self.data.insert(0, "".join(self.data))
80 del self.data[1:]
80 del self.data[1:]
81 s = self.data[0][doff:doff + count]
81 s = self.data[0][doff:doff + count]
82 self.offset += len(s)
82 self.offset += len(s)
83 ret += s
83 ret += s
84 return ret
84 return ret
85
85
86 def write(self, s):
86 def write(self, s):
87 self.data.append(str(s))
87 self.data.append(str(s))
88 self.offset += len(s)
88 self.offset += len(s)
89
89
90 def delayopener(opener, target, divert, buf):
90 def delayopener(opener, target, divert, buf):
91 def o(name, mode='r'):
91 def o(name, mode='r'):
92 if name != target:
92 if name != target:
93 return opener(name, mode)
93 return opener(name, mode)
94 if divert:
94 if divert:
95 return opener(name + ".a", mode.replace('a', 'w'))
95 return opener(name + ".a", mode.replace('a', 'w'))
96 # otherwise, divert to memory
96 # otherwise, divert to memory
97 return appender(opener(name, mode), buf)
97 return appender(opener(name, mode), buf)
98 return o
98 return o
99
99
100 class changelog(revlog.revlog):
100 class changelog(revlog.revlog):
101 def __init__(self, opener):
101 def __init__(self, opener):
102 revlog.revlog.__init__(self, opener, "00changelog.i")
102 revlog.revlog.__init__(self, opener, "00changelog.i")
103 self._realopener = opener
103 self._realopener = opener
104 self._delayed = False
104 self._delayed = False
105 self._divert = False
105 self._divert = False
106
106
107 def delayupdate(self):
107 def delayupdate(self):
108 "delay visibility of index updates to other readers"
108 "delay visibility of index updates to other readers"
109 self._delayed = True
109 self._delayed = True
110 self._divert = (len(self) == 0)
110 self._divert = (len(self) == 0)
111 self._delaybuf = []
111 self._delaybuf = []
112 self.opener = delayopener(self._realopener, self.indexfile,
112 self.opener = delayopener(self._realopener, self.indexfile,
113 self._divert, self._delaybuf)
113 self._divert, self._delaybuf)
114
114
115 def finalize(self, tr):
115 def finalize(self, tr):
116 "finalize index updates"
116 "finalize index updates"
117 self._delayed = False
117 self._delayed = False
118 self.opener = self._realopener
118 self.opener = self._realopener
119 # move redirected index data back into place
119 # move redirected index data back into place
120 if self._divert:
120 if self._divert:
121 n = self.opener(self.indexfile + ".a").name
121 nfile = self.opener(self.indexfile + ".a")
122 n = nfile.name
123 nfile.close()
122 util.rename(n, n[:-2])
124 util.rename(n, n[:-2])
123 elif self._delaybuf:
125 elif self._delaybuf:
124 fp = self.opener(self.indexfile, 'a')
126 fp = self.opener(self.indexfile, 'a')
125 fp.write("".join(self._delaybuf))
127 fp.write("".join(self._delaybuf))
126 fp.close()
128 fp.close()
127 self._delaybuf = []
129 self._delaybuf = []
128 # split when we're done
130 # split when we're done
129 self.checkinlinesize(tr)
131 self.checkinlinesize(tr)
130
132
131 def readpending(self, file):
133 def readpending(self, file):
132 r = revlog.revlog(self.opener, file)
134 r = revlog.revlog(self.opener, file)
133 self.index = r.index
135 self.index = r.index
134 self.nodemap = r.nodemap
136 self.nodemap = r.nodemap
135 self._chunkcache = r._chunkcache
137 self._chunkcache = r._chunkcache
136
138
137 def writepending(self):
139 def writepending(self):
138 "create a file containing the unfinalized state for pretxnchangegroup"
140 "create a file containing the unfinalized state for pretxnchangegroup"
139 if self._delaybuf:
141 if self._delaybuf:
140 # make a temporary copy of the index
142 # make a temporary copy of the index
141 fp1 = self._realopener(self.indexfile)
143 fp1 = self._realopener(self.indexfile)
142 fp2 = self._realopener(self.indexfile + ".a", "w")
144 fp2 = self._realopener(self.indexfile + ".a", "w")
143 fp2.write(fp1.read())
145 fp2.write(fp1.read())
144 # add pending data
146 # add pending data
145 fp2.write("".join(self._delaybuf))
147 fp2.write("".join(self._delaybuf))
146 fp2.close()
148 fp2.close()
147 # switch modes so finalize can simply rename
149 # switch modes so finalize can simply rename
148 self._delaybuf = []
150 self._delaybuf = []
149 self._divert = True
151 self._divert = True
150
152
151 if self._divert:
153 if self._divert:
152 return True
154 return True
153
155
154 return False
156 return False
155
157
156 def checkinlinesize(self, tr, fp=None):
158 def checkinlinesize(self, tr, fp=None):
157 if not self._delayed:
159 if not self._delayed:
158 revlog.revlog.checkinlinesize(self, tr, fp)
160 revlog.revlog.checkinlinesize(self, tr, fp)
159
161
160 def read(self, node):
162 def read(self, node):
161 """
163 """
162 format used:
164 format used:
163 nodeid\n : manifest node in ascii
165 nodeid\n : manifest node in ascii
164 user\n : user, no \n or \r allowed
166 user\n : user, no \n or \r allowed
165 time tz extra\n : date (time is int or float, timezone is int)
167 time tz extra\n : date (time is int or float, timezone is int)
166 : extra is metadatas, encoded and separated by '\0'
168 : extra is metadatas, encoded and separated by '\0'
167 : older versions ignore it
169 : older versions ignore it
168 files\n\n : files modified by the cset, no \n or \r allowed
170 files\n\n : files modified by the cset, no \n or \r allowed
169 (.*) : comment (free text, ideally utf-8)
171 (.*) : comment (free text, ideally utf-8)
170
172
171 changelog v0 doesn't use extra
173 changelog v0 doesn't use extra
172 """
174 """
173 text = self.revision(node)
175 text = self.revision(node)
174 if not text:
176 if not text:
175 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
177 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
176 last = text.index("\n\n")
178 last = text.index("\n\n")
177 desc = encoding.tolocal(text[last + 2:])
179 desc = encoding.tolocal(text[last + 2:])
178 l = text[:last].split('\n')
180 l = text[:last].split('\n')
179 manifest = bin(l[0])
181 manifest = bin(l[0])
180 user = encoding.tolocal(l[1])
182 user = encoding.tolocal(l[1])
181
183
182 extra_data = l[2].split(' ', 2)
184 extra_data = l[2].split(' ', 2)
183 if len(extra_data) != 3:
185 if len(extra_data) != 3:
184 time = float(extra_data.pop(0))
186 time = float(extra_data.pop(0))
185 try:
187 try:
186 # various tools did silly things with the time zone field.
188 # various tools did silly things with the time zone field.
187 timezone = int(extra_data[0])
189 timezone = int(extra_data[0])
188 except ValueError:
190 except ValueError:
189 timezone = 0
191 timezone = 0
190 extra = {}
192 extra = {}
191 else:
193 else:
192 time, timezone, extra = extra_data
194 time, timezone, extra = extra_data
193 time, timezone = float(time), int(timezone)
195 time, timezone = float(time), int(timezone)
194 extra = decodeextra(extra)
196 extra = decodeextra(extra)
195 if not extra.get('branch'):
197 if not extra.get('branch'):
196 extra['branch'] = 'default'
198 extra['branch'] = 'default'
197 files = l[3:]
199 files = l[3:]
198 return (manifest, user, (time, timezone), files, desc, extra)
200 return (manifest, user, (time, timezone), files, desc, extra)
199
201
200 def add(self, manifest, files, desc, transaction, p1, p2,
202 def add(self, manifest, files, desc, transaction, p1, p2,
201 user, date=None, extra=None):
203 user, date=None, extra=None):
202 user = user.strip()
204 user = user.strip()
203 # An empty username or a username with a "\n" will make the
205 # An empty username or a username with a "\n" will make the
204 # revision text contain two "\n\n" sequences -> corrupt
206 # revision text contain two "\n\n" sequences -> corrupt
205 # repository since read cannot unpack the revision.
207 # repository since read cannot unpack the revision.
206 if not user:
208 if not user:
207 raise error.RevlogError(_("empty username"))
209 raise error.RevlogError(_("empty username"))
208 if "\n" in user:
210 if "\n" in user:
209 raise error.RevlogError(_("username %s contains a newline")
211 raise error.RevlogError(_("username %s contains a newline")
210 % repr(user))
212 % repr(user))
211
213
212 # strip trailing whitespace and leading and trailing empty lines
214 # strip trailing whitespace and leading and trailing empty lines
213 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
215 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
214
216
215 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
217 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
216
218
217 if date:
219 if date:
218 parseddate = "%d %d" % util.parsedate(date)
220 parseddate = "%d %d" % util.parsedate(date)
219 else:
221 else:
220 parseddate = "%d %d" % util.makedate()
222 parseddate = "%d %d" % util.makedate()
221 if extra:
223 if extra:
222 branch = extra.get("branch")
224 branch = extra.get("branch")
223 if branch in ("default", ""):
225 if branch in ("default", ""):
224 del extra["branch"]
226 del extra["branch"]
225 elif branch in (".", "null", "tip"):
227 elif branch in (".", "null", "tip"):
226 raise error.RevlogError(_('the name \'%s\' is reserved')
228 raise error.RevlogError(_('the name \'%s\' is reserved')
227 % branch)
229 % branch)
228 if extra:
230 if extra:
229 extra = encodeextra(extra)
231 extra = encodeextra(extra)
230 parseddate = "%s %s" % (parseddate, extra)
232 parseddate = "%s %s" % (parseddate, extra)
231 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
233 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
232 text = "\n".join(l)
234 text = "\n".join(l)
233 return self.addrevision(text, transaction, len(self), p1, p2)
235 return self.addrevision(text, transaction, len(self), p1, p2)
General Comments 0
You need to be logged in to leave comments. Login now