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