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