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