##// END OF EJS Templates
changelog: use a Factory for default value for files...
Gregory Szorc -
r34442:50474f0b default
parent child Browse files
Show More
@@ -1,545 +1,545 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 bin,
12 bin,
13 hex,
13 hex,
14 nullid,
14 nullid,
15 )
15 )
16 from .thirdparty import (
16 from .thirdparty import (
17 attr,
17 attr,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 revlog,
23 revlog,
24 util,
24 util,
25 )
25 )
26
26
27 _defaultextra = {'branch': 'default'}
27 _defaultextra = {'branch': 'default'}
28
28
29 def _string_escape(text):
29 def _string_escape(text):
30 """
30 """
31 >>> from .pycompat import bytechr as chr
31 >>> from .pycompat import bytechr as chr
32 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
32 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
33 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
33 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
34 >>> s
34 >>> s
35 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
35 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
36 >>> res = _string_escape(s)
36 >>> res = _string_escape(s)
37 >>> s == util.unescapestr(res)
37 >>> s == util.unescapestr(res)
38 True
38 True
39 """
39 """
40 # subset of the string_escape codec
40 # subset of the string_escape codec
41 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
41 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
42 return text.replace('\0', '\\0')
42 return text.replace('\0', '\\0')
43
43
44 def decodeextra(text):
44 def decodeextra(text):
45 """
45 """
46 >>> from .pycompat import bytechr as chr
46 >>> from .pycompat import bytechr as chr
47 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
47 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
48 ... ).items())
48 ... ).items())
49 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
49 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
50 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
50 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
51 ... b'baz': chr(92) + chr(0) + b'2'})
51 ... b'baz': chr(92) + chr(0) + b'2'})
52 ... ).items())
52 ... ).items())
53 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
53 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
54 """
54 """
55 extra = _defaultextra.copy()
55 extra = _defaultextra.copy()
56 for l in text.split('\0'):
56 for l in text.split('\0'):
57 if l:
57 if l:
58 if '\\0' in l:
58 if '\\0' in l:
59 # fix up \0 without getting into trouble with \\0
59 # fix up \0 without getting into trouble with \\0
60 l = l.replace('\\\\', '\\\\\n')
60 l = l.replace('\\\\', '\\\\\n')
61 l = l.replace('\\0', '\0')
61 l = l.replace('\\0', '\0')
62 l = l.replace('\n', '')
62 l = l.replace('\n', '')
63 k, v = util.unescapestr(l).split(':', 1)
63 k, v = util.unescapestr(l).split(':', 1)
64 extra[k] = v
64 extra[k] = v
65 return extra
65 return extra
66
66
67 def encodeextra(d):
67 def encodeextra(d):
68 # keys must be sorted to produce a deterministic changelog entry
68 # keys must be sorted to produce a deterministic changelog entry
69 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
69 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
70 return "\0".join(items)
70 return "\0".join(items)
71
71
72 def stripdesc(desc):
72 def stripdesc(desc):
73 """strip trailing whitespace and leading and trailing empty lines"""
73 """strip trailing whitespace and leading and trailing empty lines"""
74 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
74 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
75
75
76 class appender(object):
76 class appender(object):
77 '''the changelog index must be updated last on disk, so we use this class
77 '''the changelog index must be updated last on disk, so we use this class
78 to delay writes to it'''
78 to delay writes to it'''
79 def __init__(self, vfs, name, mode, buf):
79 def __init__(self, vfs, name, mode, buf):
80 self.data = buf
80 self.data = buf
81 fp = vfs(name, mode)
81 fp = vfs(name, mode)
82 self.fp = fp
82 self.fp = fp
83 self.offset = fp.tell()
83 self.offset = fp.tell()
84 self.size = vfs.fstat(fp).st_size
84 self.size = vfs.fstat(fp).st_size
85 self._end = self.size
85 self._end = self.size
86
86
87 def end(self):
87 def end(self):
88 return self._end
88 return self._end
89 def tell(self):
89 def tell(self):
90 return self.offset
90 return self.offset
91 def flush(self):
91 def flush(self):
92 pass
92 pass
93 def close(self):
93 def close(self):
94 self.fp.close()
94 self.fp.close()
95
95
96 def seek(self, offset, whence=0):
96 def seek(self, offset, whence=0):
97 '''virtual file offset spans real file and data'''
97 '''virtual file offset spans real file and data'''
98 if whence == 0:
98 if whence == 0:
99 self.offset = offset
99 self.offset = offset
100 elif whence == 1:
100 elif whence == 1:
101 self.offset += offset
101 self.offset += offset
102 elif whence == 2:
102 elif whence == 2:
103 self.offset = self.end() + offset
103 self.offset = self.end() + offset
104 if self.offset < self.size:
104 if self.offset < self.size:
105 self.fp.seek(self.offset)
105 self.fp.seek(self.offset)
106
106
107 def read(self, count=-1):
107 def read(self, count=-1):
108 '''only trick here is reads that span real file and data'''
108 '''only trick here is reads that span real file and data'''
109 ret = ""
109 ret = ""
110 if self.offset < self.size:
110 if self.offset < self.size:
111 s = self.fp.read(count)
111 s = self.fp.read(count)
112 ret = s
112 ret = s
113 self.offset += len(s)
113 self.offset += len(s)
114 if count > 0:
114 if count > 0:
115 count -= len(s)
115 count -= len(s)
116 if count != 0:
116 if count != 0:
117 doff = self.offset - self.size
117 doff = self.offset - self.size
118 self.data.insert(0, "".join(self.data))
118 self.data.insert(0, "".join(self.data))
119 del self.data[1:]
119 del self.data[1:]
120 s = self.data[0][doff:doff + count]
120 s = self.data[0][doff:doff + count]
121 self.offset += len(s)
121 self.offset += len(s)
122 ret += s
122 ret += s
123 return ret
123 return ret
124
124
125 def write(self, s):
125 def write(self, s):
126 self.data.append(bytes(s))
126 self.data.append(bytes(s))
127 self.offset += len(s)
127 self.offset += len(s)
128 self._end += len(s)
128 self._end += len(s)
129
129
130 def _divertopener(opener, target):
130 def _divertopener(opener, target):
131 """build an opener that writes in 'target.a' instead of 'target'"""
131 """build an opener that writes in 'target.a' instead of 'target'"""
132 def _divert(name, mode='r', checkambig=False):
132 def _divert(name, mode='r', checkambig=False):
133 if name != target:
133 if name != target:
134 return opener(name, mode)
134 return opener(name, mode)
135 return opener(name + ".a", mode)
135 return opener(name + ".a", mode)
136 return _divert
136 return _divert
137
137
138 def _delayopener(opener, target, buf):
138 def _delayopener(opener, target, buf):
139 """build an opener that stores chunks in 'buf' instead of 'target'"""
139 """build an opener that stores chunks in 'buf' instead of 'target'"""
140 def _delay(name, mode='r', checkambig=False):
140 def _delay(name, mode='r', checkambig=False):
141 if name != target:
141 if name != target:
142 return opener(name, mode)
142 return opener(name, mode)
143 return appender(opener, name, mode, buf)
143 return appender(opener, name, mode, buf)
144 return _delay
144 return _delay
145
145
146 @attr.s
146 @attr.s
147 class _changelogrevision(object):
147 class _changelogrevision(object):
148 # Extensions might modify _defaultextra, so let the constructor below pass
148 # Extensions might modify _defaultextra, so let the constructor below pass
149 # it in
149 # it in
150 extra = attr.ib()
150 extra = attr.ib()
151 manifest = attr.ib(default=nullid)
151 manifest = attr.ib(default=nullid)
152 user = attr.ib(default='')
152 user = attr.ib(default='')
153 date = attr.ib(default=(0, 0))
153 date = attr.ib(default=(0, 0))
154 files = attr.ib(default=[])
154 files = attr.ib(default=attr.Factory(list))
155 description = attr.ib(default='')
155 description = attr.ib(default='')
156
156
157 class changelogrevision(object):
157 class changelogrevision(object):
158 """Holds results of a parsed changelog revision.
158 """Holds results of a parsed changelog revision.
159
159
160 Changelog revisions consist of multiple pieces of data, including
160 Changelog revisions consist of multiple pieces of data, including
161 the manifest node, user, and date. This object exposes a view into
161 the manifest node, user, and date. This object exposes a view into
162 the parsed object.
162 the parsed object.
163 """
163 """
164
164
165 __slots__ = (
165 __slots__ = (
166 u'_offsets',
166 u'_offsets',
167 u'_text',
167 u'_text',
168 )
168 )
169
169
170 def __new__(cls, text):
170 def __new__(cls, text):
171 if not text:
171 if not text:
172 return _changelogrevision(extra=_defaultextra)
172 return _changelogrevision(extra=_defaultextra)
173
173
174 self = super(changelogrevision, cls).__new__(cls)
174 self = super(changelogrevision, cls).__new__(cls)
175 # We could return here and implement the following as an __init__.
175 # We could return here and implement the following as an __init__.
176 # But doing it here is equivalent and saves an extra function call.
176 # But doing it here is equivalent and saves an extra function call.
177
177
178 # format used:
178 # format used:
179 # nodeid\n : manifest node in ascii
179 # nodeid\n : manifest node in ascii
180 # user\n : user, no \n or \r allowed
180 # user\n : user, no \n or \r allowed
181 # time tz extra\n : date (time is int or float, timezone is int)
181 # time tz extra\n : date (time is int or float, timezone is int)
182 # : extra is metadata, encoded and separated by '\0'
182 # : extra is metadata, encoded and separated by '\0'
183 # : older versions ignore it
183 # : older versions ignore it
184 # files\n\n : files modified by the cset, no \n or \r allowed
184 # files\n\n : files modified by the cset, no \n or \r allowed
185 # (.*) : comment (free text, ideally utf-8)
185 # (.*) : comment (free text, ideally utf-8)
186 #
186 #
187 # changelog v0 doesn't use extra
187 # changelog v0 doesn't use extra
188
188
189 nl1 = text.index('\n')
189 nl1 = text.index('\n')
190 nl2 = text.index('\n', nl1 + 1)
190 nl2 = text.index('\n', nl1 + 1)
191 nl3 = text.index('\n', nl2 + 1)
191 nl3 = text.index('\n', nl2 + 1)
192
192
193 # The list of files may be empty. Which means nl3 is the first of the
193 # The list of files may be empty. Which means nl3 is the first of the
194 # double newline that precedes the description.
194 # double newline that precedes the description.
195 if text[nl3 + 1:nl3 + 2] == '\n':
195 if text[nl3 + 1:nl3 + 2] == '\n':
196 doublenl = nl3
196 doublenl = nl3
197 else:
197 else:
198 doublenl = text.index('\n\n', nl3 + 1)
198 doublenl = text.index('\n\n', nl3 + 1)
199
199
200 self._offsets = (nl1, nl2, nl3, doublenl)
200 self._offsets = (nl1, nl2, nl3, doublenl)
201 self._text = text
201 self._text = text
202
202
203 return self
203 return self
204
204
205 @property
205 @property
206 def manifest(self):
206 def manifest(self):
207 return bin(self._text[0:self._offsets[0]])
207 return bin(self._text[0:self._offsets[0]])
208
208
209 @property
209 @property
210 def user(self):
210 def user(self):
211 off = self._offsets
211 off = self._offsets
212 return encoding.tolocal(self._text[off[0] + 1:off[1]])
212 return encoding.tolocal(self._text[off[0] + 1:off[1]])
213
213
214 @property
214 @property
215 def _rawdate(self):
215 def _rawdate(self):
216 off = self._offsets
216 off = self._offsets
217 dateextra = self._text[off[1] + 1:off[2]]
217 dateextra = self._text[off[1] + 1:off[2]]
218 return dateextra.split(' ', 2)[0:2]
218 return dateextra.split(' ', 2)[0:2]
219
219
220 @property
220 @property
221 def _rawextra(self):
221 def _rawextra(self):
222 off = self._offsets
222 off = self._offsets
223 dateextra = self._text[off[1] + 1:off[2]]
223 dateextra = self._text[off[1] + 1:off[2]]
224 fields = dateextra.split(' ', 2)
224 fields = dateextra.split(' ', 2)
225 if len(fields) != 3:
225 if len(fields) != 3:
226 return None
226 return None
227
227
228 return fields[2]
228 return fields[2]
229
229
230 @property
230 @property
231 def date(self):
231 def date(self):
232 raw = self._rawdate
232 raw = self._rawdate
233 time = float(raw[0])
233 time = float(raw[0])
234 # Various tools did silly things with the timezone.
234 # Various tools did silly things with the timezone.
235 try:
235 try:
236 timezone = int(raw[1])
236 timezone = int(raw[1])
237 except ValueError:
237 except ValueError:
238 timezone = 0
238 timezone = 0
239
239
240 return time, timezone
240 return time, timezone
241
241
242 @property
242 @property
243 def extra(self):
243 def extra(self):
244 raw = self._rawextra
244 raw = self._rawextra
245 if raw is None:
245 if raw is None:
246 return _defaultextra
246 return _defaultextra
247
247
248 return decodeextra(raw)
248 return decodeextra(raw)
249
249
250 @property
250 @property
251 def files(self):
251 def files(self):
252 off = self._offsets
252 off = self._offsets
253 if off[2] == off[3]:
253 if off[2] == off[3]:
254 return []
254 return []
255
255
256 return self._text[off[2] + 1:off[3]].split('\n')
256 return self._text[off[2] + 1:off[3]].split('\n')
257
257
258 @property
258 @property
259 def description(self):
259 def description(self):
260 return encoding.tolocal(self._text[self._offsets[3] + 2:])
260 return encoding.tolocal(self._text[self._offsets[3] + 2:])
261
261
262 class changelog(revlog.revlog):
262 class changelog(revlog.revlog):
263 def __init__(self, opener, trypending=False):
263 def __init__(self, opener, trypending=False):
264 """Load a changelog revlog using an opener.
264 """Load a changelog revlog using an opener.
265
265
266 If ``trypending`` is true, we attempt to load the index from a
266 If ``trypending`` is true, we attempt to load the index from a
267 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
267 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
268 The ``00changelog.i.a`` file contains index (and possibly inline
268 The ``00changelog.i.a`` file contains index (and possibly inline
269 revision) data for a transaction that hasn't been finalized yet.
269 revision) data for a transaction that hasn't been finalized yet.
270 It exists in a separate file to facilitate readers (such as
270 It exists in a separate file to facilitate readers (such as
271 hooks processes) accessing data before a transaction is finalized.
271 hooks processes) accessing data before a transaction is finalized.
272 """
272 """
273 if trypending and opener.exists('00changelog.i.a'):
273 if trypending and opener.exists('00changelog.i.a'):
274 indexfile = '00changelog.i.a'
274 indexfile = '00changelog.i.a'
275 else:
275 else:
276 indexfile = '00changelog.i'
276 indexfile = '00changelog.i'
277
277
278 datafile = '00changelog.d'
278 datafile = '00changelog.d'
279 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
279 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
280 checkambig=True, mmaplargeindex=True)
280 checkambig=True, mmaplargeindex=True)
281
281
282 if self._initempty:
282 if self._initempty:
283 # changelogs don't benefit from generaldelta
283 # changelogs don't benefit from generaldelta
284 self.version &= ~revlog.FLAG_GENERALDELTA
284 self.version &= ~revlog.FLAG_GENERALDELTA
285 self._generaldelta = False
285 self._generaldelta = False
286
286
287 # Delta chains for changelogs tend to be very small because entries
287 # Delta chains for changelogs tend to be very small because entries
288 # tend to be small and don't delta well with each. So disable delta
288 # tend to be small and don't delta well with each. So disable delta
289 # chains.
289 # chains.
290 self.storedeltachains = False
290 self.storedeltachains = False
291
291
292 self._realopener = opener
292 self._realopener = opener
293 self._delayed = False
293 self._delayed = False
294 self._delaybuf = None
294 self._delaybuf = None
295 self._divert = False
295 self._divert = False
296 self.filteredrevs = frozenset()
296 self.filteredrevs = frozenset()
297
297
298 def tip(self):
298 def tip(self):
299 """filtered version of revlog.tip"""
299 """filtered version of revlog.tip"""
300 for i in xrange(len(self) -1, -2, -1):
300 for i in xrange(len(self) -1, -2, -1):
301 if i not in self.filteredrevs:
301 if i not in self.filteredrevs:
302 return self.node(i)
302 return self.node(i)
303
303
304 def __contains__(self, rev):
304 def __contains__(self, rev):
305 """filtered version of revlog.__contains__"""
305 """filtered version of revlog.__contains__"""
306 return (0 <= rev < len(self)
306 return (0 <= rev < len(self)
307 and rev not in self.filteredrevs)
307 and rev not in self.filteredrevs)
308
308
309 def __iter__(self):
309 def __iter__(self):
310 """filtered version of revlog.__iter__"""
310 """filtered version of revlog.__iter__"""
311 if len(self.filteredrevs) == 0:
311 if len(self.filteredrevs) == 0:
312 return revlog.revlog.__iter__(self)
312 return revlog.revlog.__iter__(self)
313
313
314 def filterediter():
314 def filterediter():
315 for i in xrange(len(self)):
315 for i in xrange(len(self)):
316 if i not in self.filteredrevs:
316 if i not in self.filteredrevs:
317 yield i
317 yield i
318
318
319 return filterediter()
319 return filterediter()
320
320
321 def revs(self, start=0, stop=None):
321 def revs(self, start=0, stop=None):
322 """filtered version of revlog.revs"""
322 """filtered version of revlog.revs"""
323 for i in super(changelog, self).revs(start, stop):
323 for i in super(changelog, self).revs(start, stop):
324 if i not in self.filteredrevs:
324 if i not in self.filteredrevs:
325 yield i
325 yield i
326
326
327 @util.propertycache
327 @util.propertycache
328 def nodemap(self):
328 def nodemap(self):
329 # XXX need filtering too
329 # XXX need filtering too
330 self.rev(self.node(0))
330 self.rev(self.node(0))
331 return self._nodecache
331 return self._nodecache
332
332
333 def reachableroots(self, minroot, heads, roots, includepath=False):
333 def reachableroots(self, minroot, heads, roots, includepath=False):
334 return self.index.reachableroots2(minroot, heads, roots, includepath)
334 return self.index.reachableroots2(minroot, heads, roots, includepath)
335
335
336 def headrevs(self):
336 def headrevs(self):
337 if self.filteredrevs:
337 if self.filteredrevs:
338 try:
338 try:
339 return self.index.headrevsfiltered(self.filteredrevs)
339 return self.index.headrevsfiltered(self.filteredrevs)
340 # AttributeError covers non-c-extension environments and
340 # AttributeError covers non-c-extension environments and
341 # old c extensions without filter handling.
341 # old c extensions without filter handling.
342 except AttributeError:
342 except AttributeError:
343 return self._headrevs()
343 return self._headrevs()
344
344
345 return super(changelog, self).headrevs()
345 return super(changelog, self).headrevs()
346
346
347 def strip(self, *args, **kwargs):
347 def strip(self, *args, **kwargs):
348 # XXX make something better than assert
348 # XXX make something better than assert
349 # We can't expect proper strip behavior if we are filtered.
349 # We can't expect proper strip behavior if we are filtered.
350 assert not self.filteredrevs
350 assert not self.filteredrevs
351 super(changelog, self).strip(*args, **kwargs)
351 super(changelog, self).strip(*args, **kwargs)
352
352
353 def rev(self, node):
353 def rev(self, node):
354 """filtered version of revlog.rev"""
354 """filtered version of revlog.rev"""
355 r = super(changelog, self).rev(node)
355 r = super(changelog, self).rev(node)
356 if r in self.filteredrevs:
356 if r in self.filteredrevs:
357 raise error.FilteredLookupError(hex(node), self.indexfile,
357 raise error.FilteredLookupError(hex(node), self.indexfile,
358 _('filtered node'))
358 _('filtered node'))
359 return r
359 return r
360
360
361 def node(self, rev):
361 def node(self, rev):
362 """filtered version of revlog.node"""
362 """filtered version of revlog.node"""
363 if rev in self.filteredrevs:
363 if rev in self.filteredrevs:
364 raise error.FilteredIndexError(rev)
364 raise error.FilteredIndexError(rev)
365 return super(changelog, self).node(rev)
365 return super(changelog, self).node(rev)
366
366
367 def linkrev(self, rev):
367 def linkrev(self, rev):
368 """filtered version of revlog.linkrev"""
368 """filtered version of revlog.linkrev"""
369 if rev in self.filteredrevs:
369 if rev in self.filteredrevs:
370 raise error.FilteredIndexError(rev)
370 raise error.FilteredIndexError(rev)
371 return super(changelog, self).linkrev(rev)
371 return super(changelog, self).linkrev(rev)
372
372
373 def parentrevs(self, rev):
373 def parentrevs(self, rev):
374 """filtered version of revlog.parentrevs"""
374 """filtered version of revlog.parentrevs"""
375 if rev in self.filteredrevs:
375 if rev in self.filteredrevs:
376 raise error.FilteredIndexError(rev)
376 raise error.FilteredIndexError(rev)
377 return super(changelog, self).parentrevs(rev)
377 return super(changelog, self).parentrevs(rev)
378
378
379 def flags(self, rev):
379 def flags(self, rev):
380 """filtered version of revlog.flags"""
380 """filtered version of revlog.flags"""
381 if rev in self.filteredrevs:
381 if rev in self.filteredrevs:
382 raise error.FilteredIndexError(rev)
382 raise error.FilteredIndexError(rev)
383 return super(changelog, self).flags(rev)
383 return super(changelog, self).flags(rev)
384
384
385 def delayupdate(self, tr):
385 def delayupdate(self, tr):
386 "delay visibility of index updates to other readers"
386 "delay visibility of index updates to other readers"
387
387
388 if not self._delayed:
388 if not self._delayed:
389 if len(self) == 0:
389 if len(self) == 0:
390 self._divert = True
390 self._divert = True
391 if self._realopener.exists(self.indexfile + '.a'):
391 if self._realopener.exists(self.indexfile + '.a'):
392 self._realopener.unlink(self.indexfile + '.a')
392 self._realopener.unlink(self.indexfile + '.a')
393 self.opener = _divertopener(self._realopener, self.indexfile)
393 self.opener = _divertopener(self._realopener, self.indexfile)
394 else:
394 else:
395 self._delaybuf = []
395 self._delaybuf = []
396 self.opener = _delayopener(self._realopener, self.indexfile,
396 self.opener = _delayopener(self._realopener, self.indexfile,
397 self._delaybuf)
397 self._delaybuf)
398 self._delayed = True
398 self._delayed = True
399 tr.addpending('cl-%i' % id(self), self._writepending)
399 tr.addpending('cl-%i' % id(self), self._writepending)
400 tr.addfinalize('cl-%i' % id(self), self._finalize)
400 tr.addfinalize('cl-%i' % id(self), self._finalize)
401
401
402 def _finalize(self, tr):
402 def _finalize(self, tr):
403 "finalize index updates"
403 "finalize index updates"
404 self._delayed = False
404 self._delayed = False
405 self.opener = self._realopener
405 self.opener = self._realopener
406 # move redirected index data back into place
406 # move redirected index data back into place
407 if self._divert:
407 if self._divert:
408 assert not self._delaybuf
408 assert not self._delaybuf
409 tmpname = self.indexfile + ".a"
409 tmpname = self.indexfile + ".a"
410 nfile = self.opener.open(tmpname)
410 nfile = self.opener.open(tmpname)
411 nfile.close()
411 nfile.close()
412 self.opener.rename(tmpname, self.indexfile, checkambig=True)
412 self.opener.rename(tmpname, self.indexfile, checkambig=True)
413 elif self._delaybuf:
413 elif self._delaybuf:
414 fp = self.opener(self.indexfile, 'a', checkambig=True)
414 fp = self.opener(self.indexfile, 'a', checkambig=True)
415 fp.write("".join(self._delaybuf))
415 fp.write("".join(self._delaybuf))
416 fp.close()
416 fp.close()
417 self._delaybuf = None
417 self._delaybuf = None
418 self._divert = False
418 self._divert = False
419 # split when we're done
419 # split when we're done
420 self.checkinlinesize(tr)
420 self.checkinlinesize(tr)
421
421
422 def _writepending(self, tr):
422 def _writepending(self, tr):
423 "create a file containing the unfinalized state for pretxnchangegroup"
423 "create a file containing the unfinalized state for pretxnchangegroup"
424 if self._delaybuf:
424 if self._delaybuf:
425 # make a temporary copy of the index
425 # make a temporary copy of the index
426 fp1 = self._realopener(self.indexfile)
426 fp1 = self._realopener(self.indexfile)
427 pendingfilename = self.indexfile + ".a"
427 pendingfilename = self.indexfile + ".a"
428 # register as a temp file to ensure cleanup on failure
428 # register as a temp file to ensure cleanup on failure
429 tr.registertmp(pendingfilename)
429 tr.registertmp(pendingfilename)
430 # write existing data
430 # write existing data
431 fp2 = self._realopener(pendingfilename, "w")
431 fp2 = self._realopener(pendingfilename, "w")
432 fp2.write(fp1.read())
432 fp2.write(fp1.read())
433 # add pending data
433 # add pending data
434 fp2.write("".join(self._delaybuf))
434 fp2.write("".join(self._delaybuf))
435 fp2.close()
435 fp2.close()
436 # switch modes so finalize can simply rename
436 # switch modes so finalize can simply rename
437 self._delaybuf = None
437 self._delaybuf = None
438 self._divert = True
438 self._divert = True
439 self.opener = _divertopener(self._realopener, self.indexfile)
439 self.opener = _divertopener(self._realopener, self.indexfile)
440
440
441 if self._divert:
441 if self._divert:
442 return True
442 return True
443
443
444 return False
444 return False
445
445
446 def checkinlinesize(self, tr, fp=None):
446 def checkinlinesize(self, tr, fp=None):
447 if not self._delayed:
447 if not self._delayed:
448 revlog.revlog.checkinlinesize(self, tr, fp)
448 revlog.revlog.checkinlinesize(self, tr, fp)
449
449
450 def read(self, node):
450 def read(self, node):
451 """Obtain data from a parsed changelog revision.
451 """Obtain data from a parsed changelog revision.
452
452
453 Returns a 6-tuple of:
453 Returns a 6-tuple of:
454
454
455 - manifest node in binary
455 - manifest node in binary
456 - author/user as a localstr
456 - author/user as a localstr
457 - date as a 2-tuple of (time, timezone)
457 - date as a 2-tuple of (time, timezone)
458 - list of files
458 - list of files
459 - commit message as a localstr
459 - commit message as a localstr
460 - dict of extra metadata
460 - dict of extra metadata
461
461
462 Unless you need to access all fields, consider calling
462 Unless you need to access all fields, consider calling
463 ``changelogrevision`` instead, as it is faster for partial object
463 ``changelogrevision`` instead, as it is faster for partial object
464 access.
464 access.
465 """
465 """
466 c = changelogrevision(self.revision(node))
466 c = changelogrevision(self.revision(node))
467 return (
467 return (
468 c.manifest,
468 c.manifest,
469 c.user,
469 c.user,
470 c.date,
470 c.date,
471 c.files,
471 c.files,
472 c.description,
472 c.description,
473 c.extra
473 c.extra
474 )
474 )
475
475
476 def changelogrevision(self, nodeorrev):
476 def changelogrevision(self, nodeorrev):
477 """Obtain a ``changelogrevision`` for a node or revision."""
477 """Obtain a ``changelogrevision`` for a node or revision."""
478 return changelogrevision(self.revision(nodeorrev))
478 return changelogrevision(self.revision(nodeorrev))
479
479
480 def readfiles(self, node):
480 def readfiles(self, node):
481 """
481 """
482 short version of read that only returns the files modified by the cset
482 short version of read that only returns the files modified by the cset
483 """
483 """
484 text = self.revision(node)
484 text = self.revision(node)
485 if not text:
485 if not text:
486 return []
486 return []
487 last = text.index("\n\n")
487 last = text.index("\n\n")
488 l = text[:last].split('\n')
488 l = text[:last].split('\n')
489 return l[3:]
489 return l[3:]
490
490
491 def add(self, manifest, files, desc, transaction, p1, p2,
491 def add(self, manifest, files, desc, transaction, p1, p2,
492 user, date=None, extra=None):
492 user, date=None, extra=None):
493 # Convert to UTF-8 encoded bytestrings as the very first
493 # Convert to UTF-8 encoded bytestrings as the very first
494 # thing: calling any method on a localstr object will turn it
494 # thing: calling any method on a localstr object will turn it
495 # into a str object and the cached UTF-8 string is thus lost.
495 # into a str object and the cached UTF-8 string is thus lost.
496 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
496 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
497
497
498 user = user.strip()
498 user = user.strip()
499 # An empty username or a username with a "\n" will make the
499 # An empty username or a username with a "\n" will make the
500 # revision text contain two "\n\n" sequences -> corrupt
500 # revision text contain two "\n\n" sequences -> corrupt
501 # repository since read cannot unpack the revision.
501 # repository since read cannot unpack the revision.
502 if not user:
502 if not user:
503 raise error.RevlogError(_("empty username"))
503 raise error.RevlogError(_("empty username"))
504 if "\n" in user:
504 if "\n" in user:
505 raise error.RevlogError(_("username %s contains a newline")
505 raise error.RevlogError(_("username %s contains a newline")
506 % repr(user))
506 % repr(user))
507
507
508 desc = stripdesc(desc)
508 desc = stripdesc(desc)
509
509
510 if date:
510 if date:
511 parseddate = "%d %d" % util.parsedate(date)
511 parseddate = "%d %d" % util.parsedate(date)
512 else:
512 else:
513 parseddate = "%d %d" % util.makedate()
513 parseddate = "%d %d" % util.makedate()
514 if extra:
514 if extra:
515 branch = extra.get("branch")
515 branch = extra.get("branch")
516 if branch in ("default", ""):
516 if branch in ("default", ""):
517 del extra["branch"]
517 del extra["branch"]
518 elif branch in (".", "null", "tip"):
518 elif branch in (".", "null", "tip"):
519 raise error.RevlogError(_('the name \'%s\' is reserved')
519 raise error.RevlogError(_('the name \'%s\' is reserved')
520 % branch)
520 % branch)
521 if extra:
521 if extra:
522 extra = encodeextra(extra)
522 extra = encodeextra(extra)
523 parseddate = "%s %s" % (parseddate, extra)
523 parseddate = "%s %s" % (parseddate, extra)
524 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
524 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
525 text = "\n".join(l)
525 text = "\n".join(l)
526 return self.addrevision(text, transaction, len(self), p1, p2)
526 return self.addrevision(text, transaction, len(self), p1, p2)
527
527
528 def branchinfo(self, rev):
528 def branchinfo(self, rev):
529 """return the branch name and open/close state of a revision
529 """return the branch name and open/close state of a revision
530
530
531 This function exists because creating a changectx object
531 This function exists because creating a changectx object
532 just to access this is costly."""
532 just to access this is costly."""
533 extra = self.read(rev)[5]
533 extra = self.read(rev)[5]
534 return encoding.tolocal(extra.get("branch")), 'close' in extra
534 return encoding.tolocal(extra.get("branch")), 'close' in extra
535
535
536 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
536 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
537 # overlay over the standard revlog._addrevision to track the new
537 # overlay over the standard revlog._addrevision to track the new
538 # revision on the transaction.
538 # revision on the transaction.
539 rev = len(self)
539 rev = len(self)
540 node = super(changelog, self)._addrevision(node, rawtext, transaction,
540 node = super(changelog, self)._addrevision(node, rawtext, transaction,
541 *args, **kwargs)
541 *args, **kwargs)
542 revs = transaction.changes.get('revs')
542 revs = transaction.changes.get('revs')
543 if revs is not None:
543 if revs is not None:
544 revs.add(rev)
544 revs.add(rev)
545 return node
545 return node
General Comments 0
You need to be logged in to leave comments. Login now