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