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