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