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