##// END OF EJS Templates
changelog: use headrevsfiltered...
Mads Kiilerich -
r23088:fe5f044b stable
parent child Browse files
Show More
@@ -1,358 +1,358 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._delaybuf = []
131 self._divert = False
131 self._divert = False
132 self.filteredrevs = frozenset()
132 self.filteredrevs = frozenset()
133
133
134 def tip(self):
134 def tip(self):
135 """filtered version of revlog.tip"""
135 """filtered version of revlog.tip"""
136 for i in xrange(len(self) -1, -2, -1):
136 for i in xrange(len(self) -1, -2, -1):
137 if i not in self.filteredrevs:
137 if i not in self.filteredrevs:
138 return self.node(i)
138 return self.node(i)
139
139
140 def __iter__(self):
140 def __iter__(self):
141 """filtered version of revlog.__iter__"""
141 """filtered version of revlog.__iter__"""
142 if len(self.filteredrevs) == 0:
142 if len(self.filteredrevs) == 0:
143 return revlog.revlog.__iter__(self)
143 return revlog.revlog.__iter__(self)
144
144
145 def filterediter():
145 def filterediter():
146 for i in xrange(len(self)):
146 for i in xrange(len(self)):
147 if i not in self.filteredrevs:
147 if i not in self.filteredrevs:
148 yield i
148 yield i
149
149
150 return filterediter()
150 return filterediter()
151
151
152 def revs(self, start=0, stop=None):
152 def revs(self, start=0, stop=None):
153 """filtered version of revlog.revs"""
153 """filtered version of revlog.revs"""
154 for i in super(changelog, self).revs(start, stop):
154 for i in super(changelog, self).revs(start, stop):
155 if i not in self.filteredrevs:
155 if i not in self.filteredrevs:
156 yield i
156 yield i
157
157
158 @util.propertycache
158 @util.propertycache
159 def nodemap(self):
159 def nodemap(self):
160 # XXX need filtering too
160 # XXX need filtering too
161 self.rev(self.node(0))
161 self.rev(self.node(0))
162 return self._nodecache
162 return self._nodecache
163
163
164 def hasnode(self, node):
164 def hasnode(self, node):
165 """filtered version of revlog.hasnode"""
165 """filtered version of revlog.hasnode"""
166 try:
166 try:
167 i = self.rev(node)
167 i = self.rev(node)
168 return i not in self.filteredrevs
168 return i not in self.filteredrevs
169 except KeyError:
169 except KeyError:
170 return False
170 return False
171
171
172 def headrevs(self):
172 def headrevs(self):
173 if self.filteredrevs:
173 if self.filteredrevs:
174 try:
174 try:
175 return self.index.headrevs(self.filteredrevs)
175 return self.index.headrevsfiltered(self.filteredrevs)
176 # AttributeError covers non-c-extension environments.
176 # AttributeError covers non-c-extension environments and
177 # TypeError allows us work with old c extensions.
177 # old c extensions without filter handling.
178 except (AttributeError, TypeError):
178 except AttributeError:
179 return self._headrevs()
179 return self._headrevs()
180
180
181 return super(changelog, self).headrevs()
181 return super(changelog, self).headrevs()
182
182
183 def strip(self, *args, **kwargs):
183 def strip(self, *args, **kwargs):
184 # XXX make something better than assert
184 # XXX make something better than assert
185 # We can't expect proper strip behavior if we are filtered.
185 # We can't expect proper strip behavior if we are filtered.
186 assert not self.filteredrevs
186 assert not self.filteredrevs
187 super(changelog, self).strip(*args, **kwargs)
187 super(changelog, self).strip(*args, **kwargs)
188
188
189 def rev(self, node):
189 def rev(self, node):
190 """filtered version of revlog.rev"""
190 """filtered version of revlog.rev"""
191 r = super(changelog, self).rev(node)
191 r = super(changelog, self).rev(node)
192 if r in self.filteredrevs:
192 if r in self.filteredrevs:
193 raise error.FilteredLookupError(hex(node), self.indexfile,
193 raise error.FilteredLookupError(hex(node), self.indexfile,
194 _('filtered node'))
194 _('filtered node'))
195 return r
195 return r
196
196
197 def node(self, rev):
197 def node(self, rev):
198 """filtered version of revlog.node"""
198 """filtered version of revlog.node"""
199 if rev in self.filteredrevs:
199 if rev in self.filteredrevs:
200 raise error.FilteredIndexError(rev)
200 raise error.FilteredIndexError(rev)
201 return super(changelog, self).node(rev)
201 return super(changelog, self).node(rev)
202
202
203 def linkrev(self, rev):
203 def linkrev(self, rev):
204 """filtered version of revlog.linkrev"""
204 """filtered version of revlog.linkrev"""
205 if rev in self.filteredrevs:
205 if rev in self.filteredrevs:
206 raise error.FilteredIndexError(rev)
206 raise error.FilteredIndexError(rev)
207 return super(changelog, self).linkrev(rev)
207 return super(changelog, self).linkrev(rev)
208
208
209 def parentrevs(self, rev):
209 def parentrevs(self, rev):
210 """filtered version of revlog.parentrevs"""
210 """filtered version of revlog.parentrevs"""
211 if rev in self.filteredrevs:
211 if rev in self.filteredrevs:
212 raise error.FilteredIndexError(rev)
212 raise error.FilteredIndexError(rev)
213 return super(changelog, self).parentrevs(rev)
213 return super(changelog, self).parentrevs(rev)
214
214
215 def flags(self, rev):
215 def flags(self, rev):
216 """filtered version of revlog.flags"""
216 """filtered version of revlog.flags"""
217 if rev in self.filteredrevs:
217 if rev in self.filteredrevs:
218 raise error.FilteredIndexError(rev)
218 raise error.FilteredIndexError(rev)
219 return super(changelog, self).flags(rev)
219 return super(changelog, self).flags(rev)
220
220
221 def delayupdate(self):
221 def delayupdate(self):
222 "delay visibility of index updates to other readers"
222 "delay visibility of index updates to other readers"
223 self._delayed = True
223 self._delayed = True
224 self._divert = (len(self) == 0)
224 self._divert = (len(self) == 0)
225 self._delaybuf = []
225 self._delaybuf = []
226 self.opener = delayopener(self._realopener, self.indexfile,
226 self.opener = delayopener(self._realopener, self.indexfile,
227 self._divert, self._delaybuf)
227 self._divert, self._delaybuf)
228
228
229 def finalize(self, tr):
229 def finalize(self, tr):
230 "finalize index updates"
230 "finalize index updates"
231 self._delayed = False
231 self._delayed = False
232 self.opener = self._realopener
232 self.opener = self._realopener
233 # move redirected index data back into place
233 # move redirected index data back into place
234 if self._divert:
234 if self._divert:
235 tmpname = self.indexfile + ".a"
235 tmpname = self.indexfile + ".a"
236 nfile = self.opener.open(tmpname)
236 nfile = self.opener.open(tmpname)
237 nfile.close()
237 nfile.close()
238 self.opener.rename(tmpname, self.indexfile)
238 self.opener.rename(tmpname, self.indexfile)
239 elif self._delaybuf:
239 elif self._delaybuf:
240 fp = self.opener(self.indexfile, 'a')
240 fp = self.opener(self.indexfile, 'a')
241 fp.write("".join(self._delaybuf))
241 fp.write("".join(self._delaybuf))
242 fp.close()
242 fp.close()
243 self._delaybuf = []
243 self._delaybuf = []
244 # split when we're done
244 # split when we're done
245 self.checkinlinesize(tr)
245 self.checkinlinesize(tr)
246
246
247 def readpending(self, file):
247 def readpending(self, file):
248 r = revlog.revlog(self.opener, file)
248 r = revlog.revlog(self.opener, file)
249 self.index = r.index
249 self.index = r.index
250 self.nodemap = r.nodemap
250 self.nodemap = r.nodemap
251 self._nodecache = r._nodecache
251 self._nodecache = r._nodecache
252 self._chunkcache = r._chunkcache
252 self._chunkcache = r._chunkcache
253
253
254 def writepending(self):
254 def writepending(self):
255 "create a file containing the unfinalized state for pretxnchangegroup"
255 "create a file containing the unfinalized state for pretxnchangegroup"
256 if self._delaybuf:
256 if self._delaybuf:
257 # make a temporary copy of the index
257 # make a temporary copy of the index
258 fp1 = self._realopener(self.indexfile)
258 fp1 = self._realopener(self.indexfile)
259 fp2 = self._realopener(self.indexfile + ".a", "w")
259 fp2 = self._realopener(self.indexfile + ".a", "w")
260 fp2.write(fp1.read())
260 fp2.write(fp1.read())
261 # add pending data
261 # add pending data
262 fp2.write("".join(self._delaybuf))
262 fp2.write("".join(self._delaybuf))
263 fp2.close()
263 fp2.close()
264 # switch modes so finalize can simply rename
264 # switch modes so finalize can simply rename
265 self._delaybuf = []
265 self._delaybuf = []
266 self._divert = True
266 self._divert = True
267
267
268 if self._divert:
268 if self._divert:
269 return True
269 return True
270
270
271 return False
271 return False
272
272
273 def checkinlinesize(self, tr, fp=None):
273 def checkinlinesize(self, tr, fp=None):
274 if not self._delayed:
274 if not self._delayed:
275 revlog.revlog.checkinlinesize(self, tr, fp)
275 revlog.revlog.checkinlinesize(self, tr, fp)
276
276
277 def read(self, node):
277 def read(self, node):
278 """
278 """
279 format used:
279 format used:
280 nodeid\n : manifest node in ascii
280 nodeid\n : manifest node in ascii
281 user\n : user, no \n or \r allowed
281 user\n : user, no \n or \r allowed
282 time tz extra\n : date (time is int or float, timezone is int)
282 time tz extra\n : date (time is int or float, timezone is int)
283 : extra is metadata, encoded and separated by '\0'
283 : extra is metadata, encoded and separated by '\0'
284 : older versions ignore it
284 : older versions ignore it
285 files\n\n : files modified by the cset, no \n or \r allowed
285 files\n\n : files modified by the cset, no \n or \r allowed
286 (.*) : comment (free text, ideally utf-8)
286 (.*) : comment (free text, ideally utf-8)
287
287
288 changelog v0 doesn't use extra
288 changelog v0 doesn't use extra
289 """
289 """
290 text = self.revision(node)
290 text = self.revision(node)
291 if not text:
291 if not text:
292 return (nullid, "", (0, 0), [], "", _defaultextra)
292 return (nullid, "", (0, 0), [], "", _defaultextra)
293 last = text.index("\n\n")
293 last = text.index("\n\n")
294 desc = encoding.tolocal(text[last + 2:])
294 desc = encoding.tolocal(text[last + 2:])
295 l = text[:last].split('\n')
295 l = text[:last].split('\n')
296 manifest = bin(l[0])
296 manifest = bin(l[0])
297 user = encoding.tolocal(l[1])
297 user = encoding.tolocal(l[1])
298
298
299 tdata = l[2].split(' ', 2)
299 tdata = l[2].split(' ', 2)
300 if len(tdata) != 3:
300 if len(tdata) != 3:
301 time = float(tdata[0])
301 time = float(tdata[0])
302 try:
302 try:
303 # various tools did silly things with the time zone field.
303 # various tools did silly things with the time zone field.
304 timezone = int(tdata[1])
304 timezone = int(tdata[1])
305 except ValueError:
305 except ValueError:
306 timezone = 0
306 timezone = 0
307 extra = _defaultextra
307 extra = _defaultextra
308 else:
308 else:
309 time, timezone = float(tdata[0]), int(tdata[1])
309 time, timezone = float(tdata[0]), int(tdata[1])
310 extra = decodeextra(tdata[2])
310 extra = decodeextra(tdata[2])
311
311
312 files = l[3:]
312 files = l[3:]
313 return (manifest, user, (time, timezone), files, desc, extra)
313 return (manifest, user, (time, timezone), files, desc, extra)
314
314
315 def add(self, manifest, files, desc, transaction, p1, p2,
315 def add(self, manifest, files, desc, transaction, p1, p2,
316 user, date=None, extra=None):
316 user, date=None, extra=None):
317 # Convert to UTF-8 encoded bytestrings as the very first
317 # Convert to UTF-8 encoded bytestrings as the very first
318 # thing: calling any method on a localstr object will turn it
318 # thing: calling any method on a localstr object will turn it
319 # into a str object and the cached UTF-8 string is thus lost.
319 # into a str object and the cached UTF-8 string is thus lost.
320 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
320 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
321
321
322 user = user.strip()
322 user = user.strip()
323 # An empty username or a username with a "\n" will make the
323 # An empty username or a username with a "\n" will make the
324 # revision text contain two "\n\n" sequences -> corrupt
324 # revision text contain two "\n\n" sequences -> corrupt
325 # repository since read cannot unpack the revision.
325 # repository since read cannot unpack the revision.
326 if not user:
326 if not user:
327 raise error.RevlogError(_("empty username"))
327 raise error.RevlogError(_("empty username"))
328 if "\n" in user:
328 if "\n" in user:
329 raise error.RevlogError(_("username %s contains a newline")
329 raise error.RevlogError(_("username %s contains a newline")
330 % repr(user))
330 % repr(user))
331
331
332 desc = stripdesc(desc)
332 desc = stripdesc(desc)
333
333
334 if date:
334 if date:
335 parseddate = "%d %d" % util.parsedate(date)
335 parseddate = "%d %d" % util.parsedate(date)
336 else:
336 else:
337 parseddate = "%d %d" % util.makedate()
337 parseddate = "%d %d" % util.makedate()
338 if extra:
338 if extra:
339 branch = extra.get("branch")
339 branch = extra.get("branch")
340 if branch in ("default", ""):
340 if branch in ("default", ""):
341 del extra["branch"]
341 del extra["branch"]
342 elif branch in (".", "null", "tip"):
342 elif branch in (".", "null", "tip"):
343 raise error.RevlogError(_('the name \'%s\' is reserved')
343 raise error.RevlogError(_('the name \'%s\' is reserved')
344 % branch)
344 % branch)
345 if extra:
345 if extra:
346 extra = encodeextra(extra)
346 extra = encodeextra(extra)
347 parseddate = "%s %s" % (parseddate, extra)
347 parseddate = "%s %s" % (parseddate, extra)
348 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
348 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
349 text = "\n".join(l)
349 text = "\n".join(l)
350 return self.addrevision(text, transaction, len(self), p1, p2)
350 return self.addrevision(text, transaction, len(self), p1, p2)
351
351
352 def branchinfo(self, rev):
352 def branchinfo(self, rev):
353 """return the branch name and open/close state of a revision
353 """return the branch name and open/close state of a revision
354
354
355 This function exists because creating a changectx object
355 This function exists because creating a changectx object
356 just to access this is costly."""
356 just to access this is costly."""
357 extra = self.read(rev)[5]
357 extra = self.read(rev)[5]
358 return encoding.tolocal(extra.get("branch")), 'close' in extra
358 return encoding.tolocal(extra.get("branch")), 'close' in extra
General Comments 0
You need to be logged in to leave comments. Login now