##// END OF EJS Templates
changelog: introduce a 'tiprev' method...
Boris Feld -
r35689:8810f064 default
parent child Browse files
Show More
@@ -1,550 +1,555 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):
299 for i in xrange(len(self) -1, -2, -1):
300 if i not in self.filteredrevs:
301 return i
302
298 def tip(self):
303 def tip(self):
299 """filtered version of revlog.tip"""
304 """filtered version of revlog.tip"""
300 for i in xrange(len(self) -1, -2, -1):
305 for i in xrange(len(self) -1, -2, -1):
301 if i not in self.filteredrevs:
306 if i not in self.filteredrevs:
302 return self.node(i)
307 return self.node(i)
303
308
304 def __contains__(self, rev):
309 def __contains__(self, rev):
305 """filtered version of revlog.__contains__"""
310 """filtered version of revlog.__contains__"""
306 return (0 <= rev < len(self)
311 return (0 <= rev < len(self)
307 and rev not in self.filteredrevs)
312 and rev not in self.filteredrevs)
308
313
309 def __iter__(self):
314 def __iter__(self):
310 """filtered version of revlog.__iter__"""
315 """filtered version of revlog.__iter__"""
311 if len(self.filteredrevs) == 0:
316 if len(self.filteredrevs) == 0:
312 return revlog.revlog.__iter__(self)
317 return revlog.revlog.__iter__(self)
313
318
314 def filterediter():
319 def filterediter():
315 for i in xrange(len(self)):
320 for i in xrange(len(self)):
316 if i not in self.filteredrevs:
321 if i not in self.filteredrevs:
317 yield i
322 yield i
318
323
319 return filterediter()
324 return filterediter()
320
325
321 def revs(self, start=0, stop=None):
326 def revs(self, start=0, stop=None):
322 """filtered version of revlog.revs"""
327 """filtered version of revlog.revs"""
323 for i in super(changelog, self).revs(start, stop):
328 for i in super(changelog, self).revs(start, stop):
324 if i not in self.filteredrevs:
329 if i not in self.filteredrevs:
325 yield i
330 yield i
326
331
327 @util.propertycache
332 @util.propertycache
328 def nodemap(self):
333 def nodemap(self):
329 # XXX need filtering too
334 # XXX need filtering too
330 self.rev(self.node(0))
335 self.rev(self.node(0))
331 return self._nodecache
336 return self._nodecache
332
337
333 def reachableroots(self, minroot, heads, roots, includepath=False):
338 def reachableroots(self, minroot, heads, roots, includepath=False):
334 return self.index.reachableroots2(minroot, heads, roots, includepath)
339 return self.index.reachableroots2(minroot, heads, roots, includepath)
335
340
336 def headrevs(self):
341 def headrevs(self):
337 if self.filteredrevs:
342 if self.filteredrevs:
338 try:
343 try:
339 return self.index.headrevsfiltered(self.filteredrevs)
344 return self.index.headrevsfiltered(self.filteredrevs)
340 # AttributeError covers non-c-extension environments and
345 # AttributeError covers non-c-extension environments and
341 # old c extensions without filter handling.
346 # old c extensions without filter handling.
342 except AttributeError:
347 except AttributeError:
343 return self._headrevs()
348 return self._headrevs()
344
349
345 return super(changelog, self).headrevs()
350 return super(changelog, self).headrevs()
346
351
347 def strip(self, *args, **kwargs):
352 def strip(self, *args, **kwargs):
348 # XXX make something better than assert
353 # XXX make something better than assert
349 # We can't expect proper strip behavior if we are filtered.
354 # We can't expect proper strip behavior if we are filtered.
350 assert not self.filteredrevs
355 assert not self.filteredrevs
351 super(changelog, self).strip(*args, **kwargs)
356 super(changelog, self).strip(*args, **kwargs)
352
357
353 def rev(self, node):
358 def rev(self, node):
354 """filtered version of revlog.rev"""
359 """filtered version of revlog.rev"""
355 r = super(changelog, self).rev(node)
360 r = super(changelog, self).rev(node)
356 if r in self.filteredrevs:
361 if r in self.filteredrevs:
357 raise error.FilteredLookupError(hex(node), self.indexfile,
362 raise error.FilteredLookupError(hex(node), self.indexfile,
358 _('filtered node'))
363 _('filtered node'))
359 return r
364 return r
360
365
361 def node(self, rev):
366 def node(self, rev):
362 """filtered version of revlog.node"""
367 """filtered version of revlog.node"""
363 if rev in self.filteredrevs:
368 if rev in self.filteredrevs:
364 raise error.FilteredIndexError(rev)
369 raise error.FilteredIndexError(rev)
365 return super(changelog, self).node(rev)
370 return super(changelog, self).node(rev)
366
371
367 def linkrev(self, rev):
372 def linkrev(self, rev):
368 """filtered version of revlog.linkrev"""
373 """filtered version of revlog.linkrev"""
369 if rev in self.filteredrevs:
374 if rev in self.filteredrevs:
370 raise error.FilteredIndexError(rev)
375 raise error.FilteredIndexError(rev)
371 return super(changelog, self).linkrev(rev)
376 return super(changelog, self).linkrev(rev)
372
377
373 def parentrevs(self, rev):
378 def parentrevs(self, rev):
374 """filtered version of revlog.parentrevs"""
379 """filtered version of revlog.parentrevs"""
375 if rev in self.filteredrevs:
380 if rev in self.filteredrevs:
376 raise error.FilteredIndexError(rev)
381 raise error.FilteredIndexError(rev)
377 return super(changelog, self).parentrevs(rev)
382 return super(changelog, self).parentrevs(rev)
378
383
379 def flags(self, rev):
384 def flags(self, rev):
380 """filtered version of revlog.flags"""
385 """filtered version of revlog.flags"""
381 if rev in self.filteredrevs:
386 if rev in self.filteredrevs:
382 raise error.FilteredIndexError(rev)
387 raise error.FilteredIndexError(rev)
383 return super(changelog, self).flags(rev)
388 return super(changelog, self).flags(rev)
384
389
385 def delayupdate(self, tr):
390 def delayupdate(self, tr):
386 "delay visibility of index updates to other readers"
391 "delay visibility of index updates to other readers"
387
392
388 if not self._delayed:
393 if not self._delayed:
389 if len(self) == 0:
394 if len(self) == 0:
390 self._divert = True
395 self._divert = True
391 if self._realopener.exists(self.indexfile + '.a'):
396 if self._realopener.exists(self.indexfile + '.a'):
392 self._realopener.unlink(self.indexfile + '.a')
397 self._realopener.unlink(self.indexfile + '.a')
393 self.opener = _divertopener(self._realopener, self.indexfile)
398 self.opener = _divertopener(self._realopener, self.indexfile)
394 else:
399 else:
395 self._delaybuf = []
400 self._delaybuf = []
396 self.opener = _delayopener(self._realopener, self.indexfile,
401 self.opener = _delayopener(self._realopener, self.indexfile,
397 self._delaybuf)
402 self._delaybuf)
398 self._delayed = True
403 self._delayed = True
399 tr.addpending('cl-%i' % id(self), self._writepending)
404 tr.addpending('cl-%i' % id(self), self._writepending)
400 tr.addfinalize('cl-%i' % id(self), self._finalize)
405 tr.addfinalize('cl-%i' % id(self), self._finalize)
401
406
402 def _finalize(self, tr):
407 def _finalize(self, tr):
403 "finalize index updates"
408 "finalize index updates"
404 self._delayed = False
409 self._delayed = False
405 self.opener = self._realopener
410 self.opener = self._realopener
406 # move redirected index data back into place
411 # move redirected index data back into place
407 if self._divert:
412 if self._divert:
408 assert not self._delaybuf
413 assert not self._delaybuf
409 tmpname = self.indexfile + ".a"
414 tmpname = self.indexfile + ".a"
410 nfile = self.opener.open(tmpname)
415 nfile = self.opener.open(tmpname)
411 nfile.close()
416 nfile.close()
412 self.opener.rename(tmpname, self.indexfile, checkambig=True)
417 self.opener.rename(tmpname, self.indexfile, checkambig=True)
413 elif self._delaybuf:
418 elif self._delaybuf:
414 fp = self.opener(self.indexfile, 'a', checkambig=True)
419 fp = self.opener(self.indexfile, 'a', checkambig=True)
415 fp.write("".join(self._delaybuf))
420 fp.write("".join(self._delaybuf))
416 fp.close()
421 fp.close()
417 self._delaybuf = None
422 self._delaybuf = None
418 self._divert = False
423 self._divert = False
419 # split when we're done
424 # split when we're done
420 self.checkinlinesize(tr)
425 self.checkinlinesize(tr)
421
426
422 def _writepending(self, tr):
427 def _writepending(self, tr):
423 "create a file containing the unfinalized state for pretxnchangegroup"
428 "create a file containing the unfinalized state for pretxnchangegroup"
424 if self._delaybuf:
429 if self._delaybuf:
425 # make a temporary copy of the index
430 # make a temporary copy of the index
426 fp1 = self._realopener(self.indexfile)
431 fp1 = self._realopener(self.indexfile)
427 pendingfilename = self.indexfile + ".a"
432 pendingfilename = self.indexfile + ".a"
428 # register as a temp file to ensure cleanup on failure
433 # register as a temp file to ensure cleanup on failure
429 tr.registertmp(pendingfilename)
434 tr.registertmp(pendingfilename)
430 # write existing data
435 # write existing data
431 fp2 = self._realopener(pendingfilename, "w")
436 fp2 = self._realopener(pendingfilename, "w")
432 fp2.write(fp1.read())
437 fp2.write(fp1.read())
433 # add pending data
438 # add pending data
434 fp2.write("".join(self._delaybuf))
439 fp2.write("".join(self._delaybuf))
435 fp2.close()
440 fp2.close()
436 # switch modes so finalize can simply rename
441 # switch modes so finalize can simply rename
437 self._delaybuf = None
442 self._delaybuf = None
438 self._divert = True
443 self._divert = True
439 self.opener = _divertopener(self._realopener, self.indexfile)
444 self.opener = _divertopener(self._realopener, self.indexfile)
440
445
441 if self._divert:
446 if self._divert:
442 return True
447 return True
443
448
444 return False
449 return False
445
450
446 def checkinlinesize(self, tr, fp=None):
451 def checkinlinesize(self, tr, fp=None):
447 if not self._delayed:
452 if not self._delayed:
448 revlog.revlog.checkinlinesize(self, tr, fp)
453 revlog.revlog.checkinlinesize(self, tr, fp)
449
454
450 def read(self, node):
455 def read(self, node):
451 """Obtain data from a parsed changelog revision.
456 """Obtain data from a parsed changelog revision.
452
457
453 Returns a 6-tuple of:
458 Returns a 6-tuple of:
454
459
455 - manifest node in binary
460 - manifest node in binary
456 - author/user as a localstr
461 - author/user as a localstr
457 - date as a 2-tuple of (time, timezone)
462 - date as a 2-tuple of (time, timezone)
458 - list of files
463 - list of files
459 - commit message as a localstr
464 - commit message as a localstr
460 - dict of extra metadata
465 - dict of extra metadata
461
466
462 Unless you need to access all fields, consider calling
467 Unless you need to access all fields, consider calling
463 ``changelogrevision`` instead, as it is faster for partial object
468 ``changelogrevision`` instead, as it is faster for partial object
464 access.
469 access.
465 """
470 """
466 c = changelogrevision(self.revision(node))
471 c = changelogrevision(self.revision(node))
467 return (
472 return (
468 c.manifest,
473 c.manifest,
469 c.user,
474 c.user,
470 c.date,
475 c.date,
471 c.files,
476 c.files,
472 c.description,
477 c.description,
473 c.extra
478 c.extra
474 )
479 )
475
480
476 def changelogrevision(self, nodeorrev):
481 def changelogrevision(self, nodeorrev):
477 """Obtain a ``changelogrevision`` for a node or revision."""
482 """Obtain a ``changelogrevision`` for a node or revision."""
478 return changelogrevision(self.revision(nodeorrev))
483 return changelogrevision(self.revision(nodeorrev))
479
484
480 def readfiles(self, node):
485 def readfiles(self, node):
481 """
486 """
482 short version of read that only returns the files modified by the cset
487 short version of read that only returns the files modified by the cset
483 """
488 """
484 text = self.revision(node)
489 text = self.revision(node)
485 if not text:
490 if not text:
486 return []
491 return []
487 last = text.index("\n\n")
492 last = text.index("\n\n")
488 l = text[:last].split('\n')
493 l = text[:last].split('\n')
489 return l[3:]
494 return l[3:]
490
495
491 def add(self, manifest, files, desc, transaction, p1, p2,
496 def add(self, manifest, files, desc, transaction, p1, p2,
492 user, date=None, extra=None):
497 user, date=None, extra=None):
493 # Convert to UTF-8 encoded bytestrings as the very first
498 # Convert to UTF-8 encoded bytestrings as the very first
494 # thing: calling any method on a localstr object will turn it
499 # thing: calling any method on a localstr object will turn it
495 # into a str object and the cached UTF-8 string is thus lost.
500 # into a str object and the cached UTF-8 string is thus lost.
496 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
501 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
497
502
498 user = user.strip()
503 user = user.strip()
499 # An empty username or a username with a "\n" will make the
504 # An empty username or a username with a "\n" will make the
500 # revision text contain two "\n\n" sequences -> corrupt
505 # revision text contain two "\n\n" sequences -> corrupt
501 # repository since read cannot unpack the revision.
506 # repository since read cannot unpack the revision.
502 if not user:
507 if not user:
503 raise error.RevlogError(_("empty username"))
508 raise error.RevlogError(_("empty username"))
504 if "\n" in user:
509 if "\n" in user:
505 raise error.RevlogError(_("username %s contains a newline")
510 raise error.RevlogError(_("username %s contains a newline")
506 % repr(user))
511 % repr(user))
507
512
508 desc = stripdesc(desc)
513 desc = stripdesc(desc)
509
514
510 if date:
515 if date:
511 parseddate = "%d %d" % util.parsedate(date)
516 parseddate = "%d %d" % util.parsedate(date)
512 else:
517 else:
513 parseddate = "%d %d" % util.makedate()
518 parseddate = "%d %d" % util.makedate()
514 if extra:
519 if extra:
515 branch = extra.get("branch")
520 branch = extra.get("branch")
516 if branch in ("default", ""):
521 if branch in ("default", ""):
517 del extra["branch"]
522 del extra["branch"]
518 elif branch in (".", "null", "tip"):
523 elif branch in (".", "null", "tip"):
519 raise error.RevlogError(_('the name \'%s\' is reserved')
524 raise error.RevlogError(_('the name \'%s\' is reserved')
520 % branch)
525 % branch)
521 if extra:
526 if extra:
522 extra = encodeextra(extra)
527 extra = encodeextra(extra)
523 parseddate = "%s %s" % (parseddate, extra)
528 parseddate = "%s %s" % (parseddate, extra)
524 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
529 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
525 text = "\n".join(l)
530 text = "\n".join(l)
526 return self.addrevision(text, transaction, len(self), p1, p2)
531 return self.addrevision(text, transaction, len(self), p1, p2)
527
532
528 def branchinfo(self, rev):
533 def branchinfo(self, rev):
529 """return the branch name and open/close state of a revision
534 """return the branch name and open/close state of a revision
530
535
531 This function exists because creating a changectx object
536 This function exists because creating a changectx object
532 just to access this is costly."""
537 just to access this is costly."""
533 extra = self.read(rev)[5]
538 extra = self.read(rev)[5]
534 return encoding.tolocal(extra.get("branch")), 'close' in extra
539 return encoding.tolocal(extra.get("branch")), 'close' in extra
535
540
536 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
541 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
537 # overlay over the standard revlog._addrevision to track the new
542 # overlay over the standard revlog._addrevision to track the new
538 # revision on the transaction.
543 # revision on the transaction.
539 rev = len(self)
544 rev = len(self)
540 node = super(changelog, self)._addrevision(node, rawtext, transaction,
545 node = super(changelog, self)._addrevision(node, rawtext, transaction,
541 *args, **kwargs)
546 *args, **kwargs)
542 revs = transaction.changes.get('revs')
547 revs = transaction.changes.get('revs')
543 if revs is not None:
548 if revs is not None:
544 if revs:
549 if revs:
545 assert revs[-1] + 1 == rev
550 assert revs[-1] + 1 == rev
546 revs = xrange(revs[0], rev + 1)
551 revs = xrange(revs[0], rev + 1)
547 else:
552 else:
548 revs = xrange(rev, rev + 1)
553 revs = xrange(rev, rev + 1)
549 transaction.changes['revs'] = revs
554 transaction.changes['revs'] = revs
550 return node
555 return node
General Comments 0
You need to be logged in to leave comments. Login now