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