##// END OF EJS Templates
changelog: ensure changelog._delaybuf is initialized...
Pierre-Yves David -
r21544:667df8f4 stable
parent child Browse files
Show More
@@ -1,351 +1,352 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 _defaultextra = {'branch': 'default'}
12 _defaultextra = {'branch': 'default'}
13
13
14 def _string_escape(text):
14 def _string_escape(text):
15 """
15 """
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 >>> s
18 >>> s
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 >>> res = _string_escape(s)
20 >>> res = _string_escape(s)
21 >>> s == res.decode('string_escape')
21 >>> s == res.decode('string_escape')
22 True
22 True
23 """
23 """
24 # subset of the string_escape codec
24 # subset of the string_escape codec
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 return text.replace('\0', '\\0')
26 return text.replace('\0', '\\0')
27
27
28 def decodeextra(text):
28 def decodeextra(text):
29 """
29 """
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
30 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
31 ... ).iteritems())
31 ... ).iteritems())
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
32 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
33 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
34 ... 'baz': chr(92) + chr(0) + '2'})
34 ... 'baz': chr(92) + chr(0) + '2'})
35 ... ).iteritems())
35 ... ).iteritems())
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
36 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
37 """
37 """
38 extra = _defaultextra.copy()
38 extra = _defaultextra.copy()
39 for l in text.split('\0'):
39 for l in text.split('\0'):
40 if l:
40 if l:
41 if '\\0' in l:
41 if '\\0' in l:
42 # fix up \0 without getting into trouble with \\0
42 # fix up \0 without getting into trouble with \\0
43 l = l.replace('\\\\', '\\\\\n')
43 l = l.replace('\\\\', '\\\\\n')
44 l = l.replace('\\0', '\0')
44 l = l.replace('\\0', '\0')
45 l = l.replace('\n', '')
45 l = l.replace('\n', '')
46 k, v = l.decode('string_escape').split(':', 1)
46 k, v = l.decode('string_escape').split(':', 1)
47 extra[k] = v
47 extra[k] = v
48 return extra
48 return extra
49
49
50 def encodeextra(d):
50 def encodeextra(d):
51 # keys must be sorted to produce a deterministic changelog entry
51 # keys must be sorted to produce a deterministic changelog entry
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
52 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
53 return "\0".join(items)
53 return "\0".join(items)
54
54
55 def stripdesc(desc):
55 def stripdesc(desc):
56 """strip trailing whitespace and leading and trailing empty lines"""
56 """strip trailing whitespace and leading and trailing empty lines"""
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
57 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
58
58
59 class appender(object):
59 class appender(object):
60 '''the changelog index must be updated last on disk, so we use this class
60 '''the changelog index must be updated last on disk, so we use this class
61 to delay writes to it'''
61 to delay writes to it'''
62 def __init__(self, vfs, name, mode, buf):
62 def __init__(self, vfs, name, mode, buf):
63 self.data = buf
63 self.data = buf
64 fp = vfs(name, mode)
64 fp = vfs(name, mode)
65 self.fp = fp
65 self.fp = fp
66 self.offset = fp.tell()
66 self.offset = fp.tell()
67 self.size = vfs.fstat(fp).st_size
67 self.size = vfs.fstat(fp).st_size
68
68
69 def end(self):
69 def end(self):
70 return self.size + len("".join(self.data))
70 return self.size + len("".join(self.data))
71 def tell(self):
71 def tell(self):
72 return self.offset
72 return self.offset
73 def flush(self):
73 def flush(self):
74 pass
74 pass
75 def close(self):
75 def close(self):
76 self.fp.close()
76 self.fp.close()
77
77
78 def seek(self, offset, whence=0):
78 def seek(self, offset, whence=0):
79 '''virtual file offset spans real file and data'''
79 '''virtual file offset spans real file and data'''
80 if whence == 0:
80 if whence == 0:
81 self.offset = offset
81 self.offset = offset
82 elif whence == 1:
82 elif whence == 1:
83 self.offset += offset
83 self.offset += offset
84 elif whence == 2:
84 elif whence == 2:
85 self.offset = self.end() + offset
85 self.offset = self.end() + offset
86 if self.offset < self.size:
86 if self.offset < self.size:
87 self.fp.seek(self.offset)
87 self.fp.seek(self.offset)
88
88
89 def read(self, count=-1):
89 def read(self, count=-1):
90 '''only trick here is reads that span real file and data'''
90 '''only trick here is reads that span real file and data'''
91 ret = ""
91 ret = ""
92 if self.offset < self.size:
92 if self.offset < self.size:
93 s = self.fp.read(count)
93 s = self.fp.read(count)
94 ret = s
94 ret = s
95 self.offset += len(s)
95 self.offset += len(s)
96 if count > 0:
96 if count > 0:
97 count -= len(s)
97 count -= len(s)
98 if count != 0:
98 if count != 0:
99 doff = self.offset - self.size
99 doff = self.offset - self.size
100 self.data.insert(0, "".join(self.data))
100 self.data.insert(0, "".join(self.data))
101 del self.data[1:]
101 del self.data[1:]
102 s = self.data[0][doff:doff + count]
102 s = self.data[0][doff:doff + count]
103 self.offset += len(s)
103 self.offset += len(s)
104 ret += s
104 ret += s
105 return ret
105 return ret
106
106
107 def write(self, s):
107 def write(self, s):
108 self.data.append(str(s))
108 self.data.append(str(s))
109 self.offset += len(s)
109 self.offset += len(s)
110
110
111 def delayopener(opener, target, divert, buf):
111 def delayopener(opener, target, divert, buf):
112 def o(name, mode='r'):
112 def o(name, mode='r'):
113 if name != target:
113 if name != target:
114 return opener(name, mode)
114 return opener(name, mode)
115 if divert:
115 if divert:
116 return opener(name + ".a", mode.replace('a', 'w'))
116 return opener(name + ".a", mode.replace('a', 'w'))
117 # otherwise, divert to memory
117 # otherwise, divert to memory
118 return appender(opener, name, mode, buf)
118 return appender(opener, name, mode, buf)
119 return o
119 return o
120
120
121 class changelog(revlog.revlog):
121 class changelog(revlog.revlog):
122 def __init__(self, opener):
122 def __init__(self, opener):
123 revlog.revlog.__init__(self, opener, "00changelog.i")
123 revlog.revlog.__init__(self, opener, "00changelog.i")
124 if self._initempty:
124 if self._initempty:
125 # changelogs don't benefit from generaldelta
125 # changelogs don't benefit from generaldelta
126 self.version &= ~revlog.REVLOGGENERALDELTA
126 self.version &= ~revlog.REVLOGGENERALDELTA
127 self._generaldelta = False
127 self._generaldelta = False
128 self._realopener = opener
128 self._realopener = opener
129 self._delayed = False
129 self._delayed = False
130 self._delaybuf = []
130 self._divert = False
131 self._divert = False
131 self.filteredrevs = frozenset()
132 self.filteredrevs = frozenset()
132
133
133 def tip(self):
134 def tip(self):
134 """filtered version of revlog.tip"""
135 """filtered version of revlog.tip"""
135 for i in xrange(len(self) -1, -2, -1):
136 for i in xrange(len(self) -1, -2, -1):
136 if i not in self.filteredrevs:
137 if i not in self.filteredrevs:
137 return self.node(i)
138 return self.node(i)
138
139
139 def __iter__(self):
140 def __iter__(self):
140 """filtered version of revlog.__iter__"""
141 """filtered version of revlog.__iter__"""
141 if len(self.filteredrevs) == 0:
142 if len(self.filteredrevs) == 0:
142 return revlog.revlog.__iter__(self)
143 return revlog.revlog.__iter__(self)
143
144
144 def filterediter():
145 def filterediter():
145 for i in xrange(len(self)):
146 for i in xrange(len(self)):
146 if i not in self.filteredrevs:
147 if i not in self.filteredrevs:
147 yield i
148 yield i
148
149
149 return filterediter()
150 return filterediter()
150
151
151 def revs(self, start=0, stop=None):
152 def revs(self, start=0, stop=None):
152 """filtered version of revlog.revs"""
153 """filtered version of revlog.revs"""
153 for i in super(changelog, self).revs(start, stop):
154 for i in super(changelog, self).revs(start, stop):
154 if i not in self.filteredrevs:
155 if i not in self.filteredrevs:
155 yield i
156 yield i
156
157
157 @util.propertycache
158 @util.propertycache
158 def nodemap(self):
159 def nodemap(self):
159 # XXX need filtering too
160 # XXX need filtering too
160 self.rev(self.node(0))
161 self.rev(self.node(0))
161 return self._nodecache
162 return self._nodecache
162
163
163 def hasnode(self, node):
164 def hasnode(self, node):
164 """filtered version of revlog.hasnode"""
165 """filtered version of revlog.hasnode"""
165 try:
166 try:
166 i = self.rev(node)
167 i = self.rev(node)
167 return i not in self.filteredrevs
168 return i not in self.filteredrevs
168 except KeyError:
169 except KeyError:
169 return False
170 return False
170
171
171 def headrevs(self):
172 def headrevs(self):
172 if self.filteredrevs:
173 if self.filteredrevs:
173 # XXX we should fix and use the C version
174 # XXX we should fix and use the C version
174 return self._headrevs()
175 return self._headrevs()
175 return super(changelog, self).headrevs()
176 return super(changelog, self).headrevs()
176
177
177 def strip(self, *args, **kwargs):
178 def strip(self, *args, **kwargs):
178 # XXX make something better than assert
179 # XXX make something better than assert
179 # We can't expect proper strip behavior if we are filtered.
180 # We can't expect proper strip behavior if we are filtered.
180 assert not self.filteredrevs
181 assert not self.filteredrevs
181 super(changelog, self).strip(*args, **kwargs)
182 super(changelog, self).strip(*args, **kwargs)
182
183
183 def rev(self, node):
184 def rev(self, node):
184 """filtered version of revlog.rev"""
185 """filtered version of revlog.rev"""
185 r = super(changelog, self).rev(node)
186 r = super(changelog, self).rev(node)
186 if r in self.filteredrevs:
187 if r in self.filteredrevs:
187 raise error.LookupError(hex(node), self.indexfile, _('no node'))
188 raise error.LookupError(hex(node), self.indexfile, _('no node'))
188 return r
189 return r
189
190
190 def node(self, rev):
191 def node(self, rev):
191 """filtered version of revlog.node"""
192 """filtered version of revlog.node"""
192 if rev in self.filteredrevs:
193 if rev in self.filteredrevs:
193 raise IndexError(rev)
194 raise IndexError(rev)
194 return super(changelog, self).node(rev)
195 return super(changelog, self).node(rev)
195
196
196 def linkrev(self, rev):
197 def linkrev(self, rev):
197 """filtered version of revlog.linkrev"""
198 """filtered version of revlog.linkrev"""
198 if rev in self.filteredrevs:
199 if rev in self.filteredrevs:
199 raise IndexError(rev)
200 raise IndexError(rev)
200 return super(changelog, self).linkrev(rev)
201 return super(changelog, self).linkrev(rev)
201
202
202 def parentrevs(self, rev):
203 def parentrevs(self, rev):
203 """filtered version of revlog.parentrevs"""
204 """filtered version of revlog.parentrevs"""
204 if rev in self.filteredrevs:
205 if rev in self.filteredrevs:
205 raise IndexError(rev)
206 raise IndexError(rev)
206 return super(changelog, self).parentrevs(rev)
207 return super(changelog, self).parentrevs(rev)
207
208
208 def flags(self, rev):
209 def flags(self, rev):
209 """filtered version of revlog.flags"""
210 """filtered version of revlog.flags"""
210 if rev in self.filteredrevs:
211 if rev in self.filteredrevs:
211 raise IndexError(rev)
212 raise IndexError(rev)
212 return super(changelog, self).flags(rev)
213 return super(changelog, self).flags(rev)
213
214
214 def delayupdate(self):
215 def delayupdate(self):
215 "delay visibility of index updates to other readers"
216 "delay visibility of index updates to other readers"
216 self._delayed = True
217 self._delayed = True
217 self._divert = (len(self) == 0)
218 self._divert = (len(self) == 0)
218 self._delaybuf = []
219 self._delaybuf = []
219 self.opener = delayopener(self._realopener, self.indexfile,
220 self.opener = delayopener(self._realopener, self.indexfile,
220 self._divert, self._delaybuf)
221 self._divert, self._delaybuf)
221
222
222 def finalize(self, tr):
223 def finalize(self, tr):
223 "finalize index updates"
224 "finalize index updates"
224 self._delayed = False
225 self._delayed = False
225 self.opener = self._realopener
226 self.opener = self._realopener
226 # move redirected index data back into place
227 # move redirected index data back into place
227 if self._divert:
228 if self._divert:
228 tmpname = self.indexfile + ".a"
229 tmpname = self.indexfile + ".a"
229 nfile = self.opener.open(tmpname)
230 nfile = self.opener.open(tmpname)
230 nfile.close()
231 nfile.close()
231 self.opener.rename(tmpname, self.indexfile)
232 self.opener.rename(tmpname, self.indexfile)
232 elif self._delaybuf:
233 elif self._delaybuf:
233 fp = self.opener(self.indexfile, 'a')
234 fp = self.opener(self.indexfile, 'a')
234 fp.write("".join(self._delaybuf))
235 fp.write("".join(self._delaybuf))
235 fp.close()
236 fp.close()
236 self._delaybuf = []
237 self._delaybuf = []
237 # split when we're done
238 # split when we're done
238 self.checkinlinesize(tr)
239 self.checkinlinesize(tr)
239
240
240 def readpending(self, file):
241 def readpending(self, file):
241 r = revlog.revlog(self.opener, file)
242 r = revlog.revlog(self.opener, file)
242 self.index = r.index
243 self.index = r.index
243 self.nodemap = r.nodemap
244 self.nodemap = r.nodemap
244 self._nodecache = r._nodecache
245 self._nodecache = r._nodecache
245 self._chunkcache = r._chunkcache
246 self._chunkcache = r._chunkcache
246
247
247 def writepending(self):
248 def writepending(self):
248 "create a file containing the unfinalized state for pretxnchangegroup"
249 "create a file containing the unfinalized state for pretxnchangegroup"
249 if self._delaybuf:
250 if self._delaybuf:
250 # make a temporary copy of the index
251 # make a temporary copy of the index
251 fp1 = self._realopener(self.indexfile)
252 fp1 = self._realopener(self.indexfile)
252 fp2 = self._realopener(self.indexfile + ".a", "w")
253 fp2 = self._realopener(self.indexfile + ".a", "w")
253 fp2.write(fp1.read())
254 fp2.write(fp1.read())
254 # add pending data
255 # add pending data
255 fp2.write("".join(self._delaybuf))
256 fp2.write("".join(self._delaybuf))
256 fp2.close()
257 fp2.close()
257 # switch modes so finalize can simply rename
258 # switch modes so finalize can simply rename
258 self._delaybuf = []
259 self._delaybuf = []
259 self._divert = True
260 self._divert = True
260
261
261 if self._divert:
262 if self._divert:
262 return True
263 return True
263
264
264 return False
265 return False
265
266
266 def checkinlinesize(self, tr, fp=None):
267 def checkinlinesize(self, tr, fp=None):
267 if not self._delayed:
268 if not self._delayed:
268 revlog.revlog.checkinlinesize(self, tr, fp)
269 revlog.revlog.checkinlinesize(self, tr, fp)
269
270
270 def read(self, node):
271 def read(self, node):
271 """
272 """
272 format used:
273 format used:
273 nodeid\n : manifest node in ascii
274 nodeid\n : manifest node in ascii
274 user\n : user, no \n or \r allowed
275 user\n : user, no \n or \r allowed
275 time tz extra\n : date (time is int or float, timezone is int)
276 time tz extra\n : date (time is int or float, timezone is int)
276 : extra is metadata, encoded and separated by '\0'
277 : extra is metadata, encoded and separated by '\0'
277 : older versions ignore it
278 : older versions ignore it
278 files\n\n : files modified by the cset, no \n or \r allowed
279 files\n\n : files modified by the cset, no \n or \r allowed
279 (.*) : comment (free text, ideally utf-8)
280 (.*) : comment (free text, ideally utf-8)
280
281
281 changelog v0 doesn't use extra
282 changelog v0 doesn't use extra
282 """
283 """
283 text = self.revision(node)
284 text = self.revision(node)
284 if not text:
285 if not text:
285 return (nullid, "", (0, 0), [], "", _defaultextra)
286 return (nullid, "", (0, 0), [], "", _defaultextra)
286 last = text.index("\n\n")
287 last = text.index("\n\n")
287 desc = encoding.tolocal(text[last + 2:])
288 desc = encoding.tolocal(text[last + 2:])
288 l = text[:last].split('\n')
289 l = text[:last].split('\n')
289 manifest = bin(l[0])
290 manifest = bin(l[0])
290 user = encoding.tolocal(l[1])
291 user = encoding.tolocal(l[1])
291
292
292 tdata = l[2].split(' ', 2)
293 tdata = l[2].split(' ', 2)
293 if len(tdata) != 3:
294 if len(tdata) != 3:
294 time = float(tdata[0])
295 time = float(tdata[0])
295 try:
296 try:
296 # various tools did silly things with the time zone field.
297 # various tools did silly things with the time zone field.
297 timezone = int(tdata[1])
298 timezone = int(tdata[1])
298 except ValueError:
299 except ValueError:
299 timezone = 0
300 timezone = 0
300 extra = _defaultextra
301 extra = _defaultextra
301 else:
302 else:
302 time, timezone = float(tdata[0]), int(tdata[1])
303 time, timezone = float(tdata[0]), int(tdata[1])
303 extra = decodeextra(tdata[2])
304 extra = decodeextra(tdata[2])
304
305
305 files = l[3:]
306 files = l[3:]
306 return (manifest, user, (time, timezone), files, desc, extra)
307 return (manifest, user, (time, timezone), files, desc, extra)
307
308
308 def add(self, manifest, files, desc, transaction, p1, p2,
309 def add(self, manifest, files, desc, transaction, p1, p2,
309 user, date=None, extra=None):
310 user, date=None, extra=None):
310 # Convert to UTF-8 encoded bytestrings as the very first
311 # Convert to UTF-8 encoded bytestrings as the very first
311 # thing: calling any method on a localstr object will turn it
312 # thing: calling any method on a localstr object will turn it
312 # into a str object and the cached UTF-8 string is thus lost.
313 # into a str object and the cached UTF-8 string is thus lost.
313 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
314 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
314
315
315 user = user.strip()
316 user = user.strip()
316 # An empty username or a username with a "\n" will make the
317 # An empty username or a username with a "\n" will make the
317 # revision text contain two "\n\n" sequences -> corrupt
318 # revision text contain two "\n\n" sequences -> corrupt
318 # repository since read cannot unpack the revision.
319 # repository since read cannot unpack the revision.
319 if not user:
320 if not user:
320 raise error.RevlogError(_("empty username"))
321 raise error.RevlogError(_("empty username"))
321 if "\n" in user:
322 if "\n" in user:
322 raise error.RevlogError(_("username %s contains a newline")
323 raise error.RevlogError(_("username %s contains a newline")
323 % repr(user))
324 % repr(user))
324
325
325 desc = stripdesc(desc)
326 desc = stripdesc(desc)
326
327
327 if date:
328 if date:
328 parseddate = "%d %d" % util.parsedate(date)
329 parseddate = "%d %d" % util.parsedate(date)
329 else:
330 else:
330 parseddate = "%d %d" % util.makedate()
331 parseddate = "%d %d" % util.makedate()
331 if extra:
332 if extra:
332 branch = extra.get("branch")
333 branch = extra.get("branch")
333 if branch in ("default", ""):
334 if branch in ("default", ""):
334 del extra["branch"]
335 del extra["branch"]
335 elif branch in (".", "null", "tip"):
336 elif branch in (".", "null", "tip"):
336 raise error.RevlogError(_('the name \'%s\' is reserved')
337 raise error.RevlogError(_('the name \'%s\' is reserved')
337 % branch)
338 % branch)
338 if extra:
339 if extra:
339 extra = encodeextra(extra)
340 extra = encodeextra(extra)
340 parseddate = "%s %s" % (parseddate, extra)
341 parseddate = "%s %s" % (parseddate, extra)
341 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
342 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
342 text = "\n".join(l)
343 text = "\n".join(l)
343 return self.addrevision(text, transaction, len(self), p1, p2)
344 return self.addrevision(text, transaction, len(self), p1, p2)
344
345
345 def branchinfo(self, rev):
346 def branchinfo(self, rev):
346 """return the branch name and open/close state of a revision
347 """return the branch name and open/close state of a revision
347
348
348 This function exists because creating a changectx object
349 This function exists because creating a changectx object
349 just to access this is costly."""
350 just to access this is costly."""
350 extra = self.read(rev)[5]
351 extra = self.read(rev)[5]
351 return encoding.tolocal(extra.get("branch")), 'close' in extra
352 return encoding.tolocal(extra.get("branch")), 'close' in extra
General Comments 0
You need to be logged in to leave comments. Login now