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