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