##// END OF EJS Templates
py3: use bytes() to cast to immutable bytes in changelog.appender.write()
Yuya Nishihara -
r31642:addc392c default
parent child Browse files
Show More
@@ -1,537 +1,537 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 = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
30 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
31 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
31 >>> s = "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({'foo': 'bar', 'baz': chr(0) + '2'})
44 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
45 ... ).iteritems())
45 ... ).iteritems())
46 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
46 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
47 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
47 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
48 ... 'baz': chr(92) + chr(0) + '2'})
48 ... 'baz': chr(92) + chr(0) + '2'})
49 ... ).iteritems())
49 ... ).iteritems())
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(str(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] == '\n':
193 if text[nl3 + 1] == '\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):
261 def __init__(self, opener):
262 revlog.revlog.__init__(self, opener, "00changelog.i",
262 revlog.revlog.__init__(self, opener, "00changelog.i",
263 checkambig=True)
263 checkambig=True)
264 if self._initempty:
264 if self._initempty:
265 # changelogs don't benefit from generaldelta
265 # changelogs don't benefit from generaldelta
266 self.version &= ~revlog.REVLOGGENERALDELTA
266 self.version &= ~revlog.REVLOGGENERALDELTA
267 self._generaldelta = False
267 self._generaldelta = False
268
268
269 # Delta chains for changelogs tend to be very small because entries
269 # Delta chains for changelogs tend to be very small because entries
270 # tend to be small and don't delta well with each. So disable delta
270 # tend to be small and don't delta well with each. So disable delta
271 # chains.
271 # chains.
272 self.storedeltachains = False
272 self.storedeltachains = False
273
273
274 self._realopener = opener
274 self._realopener = opener
275 self._delayed = False
275 self._delayed = False
276 self._delaybuf = None
276 self._delaybuf = None
277 self._divert = False
277 self._divert = False
278 self.filteredrevs = frozenset()
278 self.filteredrevs = frozenset()
279
279
280 def tip(self):
280 def tip(self):
281 """filtered version of revlog.tip"""
281 """filtered version of revlog.tip"""
282 for i in xrange(len(self) -1, -2, -1):
282 for i in xrange(len(self) -1, -2, -1):
283 if i not in self.filteredrevs:
283 if i not in self.filteredrevs:
284 return self.node(i)
284 return self.node(i)
285
285
286 def __contains__(self, rev):
286 def __contains__(self, rev):
287 """filtered version of revlog.__contains__"""
287 """filtered version of revlog.__contains__"""
288 return (0 <= rev < len(self)
288 return (0 <= rev < len(self)
289 and rev not in self.filteredrevs)
289 and rev not in self.filteredrevs)
290
290
291 def __iter__(self):
291 def __iter__(self):
292 """filtered version of revlog.__iter__"""
292 """filtered version of revlog.__iter__"""
293 if len(self.filteredrevs) == 0:
293 if len(self.filteredrevs) == 0:
294 return revlog.revlog.__iter__(self)
294 return revlog.revlog.__iter__(self)
295
295
296 def filterediter():
296 def filterediter():
297 for i in xrange(len(self)):
297 for i in xrange(len(self)):
298 if i not in self.filteredrevs:
298 if i not in self.filteredrevs:
299 yield i
299 yield i
300
300
301 return filterediter()
301 return filterediter()
302
302
303 def revs(self, start=0, stop=None):
303 def revs(self, start=0, stop=None):
304 """filtered version of revlog.revs"""
304 """filtered version of revlog.revs"""
305 for i in super(changelog, self).revs(start, stop):
305 for i in super(changelog, self).revs(start, stop):
306 if i not in self.filteredrevs:
306 if i not in self.filteredrevs:
307 yield i
307 yield i
308
308
309 @util.propertycache
309 @util.propertycache
310 def nodemap(self):
310 def nodemap(self):
311 # XXX need filtering too
311 # XXX need filtering too
312 self.rev(self.node(0))
312 self.rev(self.node(0))
313 return self._nodecache
313 return self._nodecache
314
314
315 def reachableroots(self, minroot, heads, roots, includepath=False):
315 def reachableroots(self, minroot, heads, roots, includepath=False):
316 return self.index.reachableroots2(minroot, heads, roots, includepath)
316 return self.index.reachableroots2(minroot, heads, roots, includepath)
317
317
318 def headrevs(self):
318 def headrevs(self):
319 if self.filteredrevs:
319 if self.filteredrevs:
320 try:
320 try:
321 return self.index.headrevsfiltered(self.filteredrevs)
321 return self.index.headrevsfiltered(self.filteredrevs)
322 # AttributeError covers non-c-extension environments and
322 # AttributeError covers non-c-extension environments and
323 # old c extensions without filter handling.
323 # old c extensions without filter handling.
324 except AttributeError:
324 except AttributeError:
325 return self._headrevs()
325 return self._headrevs()
326
326
327 return super(changelog, self).headrevs()
327 return super(changelog, self).headrevs()
328
328
329 def strip(self, *args, **kwargs):
329 def strip(self, *args, **kwargs):
330 # XXX make something better than assert
330 # XXX make something better than assert
331 # We can't expect proper strip behavior if we are filtered.
331 # We can't expect proper strip behavior if we are filtered.
332 assert not self.filteredrevs
332 assert not self.filteredrevs
333 super(changelog, self).strip(*args, **kwargs)
333 super(changelog, self).strip(*args, **kwargs)
334
334
335 def rev(self, node):
335 def rev(self, node):
336 """filtered version of revlog.rev"""
336 """filtered version of revlog.rev"""
337 r = super(changelog, self).rev(node)
337 r = super(changelog, self).rev(node)
338 if r in self.filteredrevs:
338 if r in self.filteredrevs:
339 raise error.FilteredLookupError(hex(node), self.indexfile,
339 raise error.FilteredLookupError(hex(node), self.indexfile,
340 _('filtered node'))
340 _('filtered node'))
341 return r
341 return r
342
342
343 def node(self, rev):
343 def node(self, rev):
344 """filtered version of revlog.node"""
344 """filtered version of revlog.node"""
345 if rev in self.filteredrevs:
345 if rev in self.filteredrevs:
346 raise error.FilteredIndexError(rev)
346 raise error.FilteredIndexError(rev)
347 return super(changelog, self).node(rev)
347 return super(changelog, self).node(rev)
348
348
349 def linkrev(self, rev):
349 def linkrev(self, rev):
350 """filtered version of revlog.linkrev"""
350 """filtered version of revlog.linkrev"""
351 if rev in self.filteredrevs:
351 if rev in self.filteredrevs:
352 raise error.FilteredIndexError(rev)
352 raise error.FilteredIndexError(rev)
353 return super(changelog, self).linkrev(rev)
353 return super(changelog, self).linkrev(rev)
354
354
355 def parentrevs(self, rev):
355 def parentrevs(self, rev):
356 """filtered version of revlog.parentrevs"""
356 """filtered version of revlog.parentrevs"""
357 if rev in self.filteredrevs:
357 if rev in self.filteredrevs:
358 raise error.FilteredIndexError(rev)
358 raise error.FilteredIndexError(rev)
359 return super(changelog, self).parentrevs(rev)
359 return super(changelog, self).parentrevs(rev)
360
360
361 def flags(self, rev):
361 def flags(self, rev):
362 """filtered version of revlog.flags"""
362 """filtered version of revlog.flags"""
363 if rev in self.filteredrevs:
363 if rev in self.filteredrevs:
364 raise error.FilteredIndexError(rev)
364 raise error.FilteredIndexError(rev)
365 return super(changelog, self).flags(rev)
365 return super(changelog, self).flags(rev)
366
366
367 def delayupdate(self, tr):
367 def delayupdate(self, tr):
368 "delay visibility of index updates to other readers"
368 "delay visibility of index updates to other readers"
369
369
370 if not self._delayed:
370 if not self._delayed:
371 if len(self) == 0:
371 if len(self) == 0:
372 self._divert = True
372 self._divert = True
373 if self._realopener.exists(self.indexfile + '.a'):
373 if self._realopener.exists(self.indexfile + '.a'):
374 self._realopener.unlink(self.indexfile + '.a')
374 self._realopener.unlink(self.indexfile + '.a')
375 self.opener = _divertopener(self._realopener, self.indexfile)
375 self.opener = _divertopener(self._realopener, self.indexfile)
376 else:
376 else:
377 self._delaybuf = []
377 self._delaybuf = []
378 self.opener = _delayopener(self._realopener, self.indexfile,
378 self.opener = _delayopener(self._realopener, self.indexfile,
379 self._delaybuf)
379 self._delaybuf)
380 self._delayed = True
380 self._delayed = True
381 tr.addpending('cl-%i' % id(self), self._writepending)
381 tr.addpending('cl-%i' % id(self), self._writepending)
382 tr.addfinalize('cl-%i' % id(self), self._finalize)
382 tr.addfinalize('cl-%i' % id(self), self._finalize)
383
383
384 def _finalize(self, tr):
384 def _finalize(self, tr):
385 "finalize index updates"
385 "finalize index updates"
386 self._delayed = False
386 self._delayed = False
387 self.opener = self._realopener
387 self.opener = self._realopener
388 # move redirected index data back into place
388 # move redirected index data back into place
389 if self._divert:
389 if self._divert:
390 assert not self._delaybuf
390 assert not self._delaybuf
391 tmpname = self.indexfile + ".a"
391 tmpname = self.indexfile + ".a"
392 nfile = self.opener.open(tmpname)
392 nfile = self.opener.open(tmpname)
393 nfile.close()
393 nfile.close()
394 self.opener.rename(tmpname, self.indexfile, checkambig=True)
394 self.opener.rename(tmpname, self.indexfile, checkambig=True)
395 elif self._delaybuf:
395 elif self._delaybuf:
396 fp = self.opener(self.indexfile, 'a', checkambig=True)
396 fp = self.opener(self.indexfile, 'a', checkambig=True)
397 fp.write("".join(self._delaybuf))
397 fp.write("".join(self._delaybuf))
398 fp.close()
398 fp.close()
399 self._delaybuf = None
399 self._delaybuf = None
400 self._divert = False
400 self._divert = False
401 # split when we're done
401 # split when we're done
402 self.checkinlinesize(tr)
402 self.checkinlinesize(tr)
403
403
404 def readpending(self, file):
404 def readpending(self, file):
405 """read index data from a "pending" file
405 """read index data from a "pending" file
406
406
407 During a transaction, the actual changeset data is already stored in the
407 During a transaction, the actual changeset data is already stored in the
408 main file, but not yet finalized in the on-disk index. Instead, a
408 main file, but not yet finalized in the on-disk index. Instead, a
409 "pending" index is written by the transaction logic. If this function
409 "pending" index is written by the transaction logic. If this function
410 is running, we are likely in a subprocess invoked in a hook. The
410 is running, we are likely in a subprocess invoked in a hook. The
411 subprocess is informed that it is within a transaction and needs to
411 subprocess is informed that it is within a transaction and needs to
412 access its content.
412 access its content.
413
413
414 This function will read all the index data out of the pending file and
414 This function will read all the index data out of the pending file and
415 overwrite the main index."""
415 overwrite the main index."""
416
416
417 if not self.opener.exists(file):
417 if not self.opener.exists(file):
418 return # no pending data for changelog
418 return # no pending data for changelog
419 r = revlog.revlog(self.opener, file)
419 r = revlog.revlog(self.opener, file)
420 self.index = r.index
420 self.index = r.index
421 self.nodemap = r.nodemap
421 self.nodemap = r.nodemap
422 self._nodecache = r._nodecache
422 self._nodecache = r._nodecache
423 self._chunkcache = r._chunkcache
423 self._chunkcache = r._chunkcache
424
424
425 def _writepending(self, tr):
425 def _writepending(self, tr):
426 "create a file containing the unfinalized state for pretxnchangegroup"
426 "create a file containing the unfinalized state for pretxnchangegroup"
427 if self._delaybuf:
427 if self._delaybuf:
428 # make a temporary copy of the index
428 # make a temporary copy of the index
429 fp1 = self._realopener(self.indexfile)
429 fp1 = self._realopener(self.indexfile)
430 pendingfilename = self.indexfile + ".a"
430 pendingfilename = self.indexfile + ".a"
431 # register as a temp file to ensure cleanup on failure
431 # register as a temp file to ensure cleanup on failure
432 tr.registertmp(pendingfilename)
432 tr.registertmp(pendingfilename)
433 # write existing data
433 # write existing data
434 fp2 = self._realopener(pendingfilename, "w")
434 fp2 = self._realopener(pendingfilename, "w")
435 fp2.write(fp1.read())
435 fp2.write(fp1.read())
436 # add pending data
436 # add pending data
437 fp2.write("".join(self._delaybuf))
437 fp2.write("".join(self._delaybuf))
438 fp2.close()
438 fp2.close()
439 # switch modes so finalize can simply rename
439 # switch modes so finalize can simply rename
440 self._delaybuf = None
440 self._delaybuf = None
441 self._divert = True
441 self._divert = True
442 self.opener = _divertopener(self._realopener, self.indexfile)
442 self.opener = _divertopener(self._realopener, self.indexfile)
443
443
444 if self._divert:
444 if self._divert:
445 return True
445 return True
446
446
447 return False
447 return False
448
448
449 def checkinlinesize(self, tr, fp=None):
449 def checkinlinesize(self, tr, fp=None):
450 if not self._delayed:
450 if not self._delayed:
451 revlog.revlog.checkinlinesize(self, tr, fp)
451 revlog.revlog.checkinlinesize(self, tr, fp)
452
452
453 def read(self, node):
453 def read(self, node):
454 """Obtain data from a parsed changelog revision.
454 """Obtain data from a parsed changelog revision.
455
455
456 Returns a 6-tuple of:
456 Returns a 6-tuple of:
457
457
458 - manifest node in binary
458 - manifest node in binary
459 - author/user as a localstr
459 - author/user as a localstr
460 - date as a 2-tuple of (time, timezone)
460 - date as a 2-tuple of (time, timezone)
461 - list of files
461 - list of files
462 - commit message as a localstr
462 - commit message as a localstr
463 - dict of extra metadata
463 - dict of extra metadata
464
464
465 Unless you need to access all fields, consider calling
465 Unless you need to access all fields, consider calling
466 ``changelogrevision`` instead, as it is faster for partial object
466 ``changelogrevision`` instead, as it is faster for partial object
467 access.
467 access.
468 """
468 """
469 c = changelogrevision(self.revision(node))
469 c = changelogrevision(self.revision(node))
470 return (
470 return (
471 c.manifest,
471 c.manifest,
472 c.user,
472 c.user,
473 c.date,
473 c.date,
474 c.files,
474 c.files,
475 c.description,
475 c.description,
476 c.extra
476 c.extra
477 )
477 )
478
478
479 def changelogrevision(self, nodeorrev):
479 def changelogrevision(self, nodeorrev):
480 """Obtain a ``changelogrevision`` for a node or revision."""
480 """Obtain a ``changelogrevision`` for a node or revision."""
481 return changelogrevision(self.revision(nodeorrev))
481 return changelogrevision(self.revision(nodeorrev))
482
482
483 def readfiles(self, node):
483 def readfiles(self, node):
484 """
484 """
485 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
486 """
486 """
487 text = self.revision(node)
487 text = self.revision(node)
488 if not text:
488 if not text:
489 return []
489 return []
490 last = text.index("\n\n")
490 last = text.index("\n\n")
491 l = text[:last].split('\n')
491 l = text[:last].split('\n')
492 return l[3:]
492 return l[3:]
493
493
494 def add(self, manifest, files, desc, transaction, p1, p2,
494 def add(self, manifest, files, desc, transaction, p1, p2,
495 user, date=None, extra=None):
495 user, date=None, extra=None):
496 # Convert to UTF-8 encoded bytestrings as the very first
496 # Convert to UTF-8 encoded bytestrings as the very first
497 # thing: calling any method on a localstr object will turn it
497 # thing: calling any method on a localstr object will turn it
498 # into a str object and the cached UTF-8 string is thus lost.
498 # into a str object and the cached UTF-8 string is thus lost.
499 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
499 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
500
500
501 user = user.strip()
501 user = user.strip()
502 # 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
503 # revision text contain two "\n\n" sequences -> corrupt
503 # revision text contain two "\n\n" sequences -> corrupt
504 # repository since read cannot unpack the revision.
504 # repository since read cannot unpack the revision.
505 if not user:
505 if not user:
506 raise error.RevlogError(_("empty username"))
506 raise error.RevlogError(_("empty username"))
507 if "\n" in user:
507 if "\n" in user:
508 raise error.RevlogError(_("username %s contains a newline")
508 raise error.RevlogError(_("username %s contains a newline")
509 % repr(user))
509 % repr(user))
510
510
511 desc = stripdesc(desc)
511 desc = stripdesc(desc)
512
512
513 if date:
513 if date:
514 parseddate = "%d %d" % util.parsedate(date)
514 parseddate = "%d %d" % util.parsedate(date)
515 else:
515 else:
516 parseddate = "%d %d" % util.makedate()
516 parseddate = "%d %d" % util.makedate()
517 if extra:
517 if extra:
518 branch = extra.get("branch")
518 branch = extra.get("branch")
519 if branch in ("default", ""):
519 if branch in ("default", ""):
520 del extra["branch"]
520 del extra["branch"]
521 elif branch in (".", "null", "tip"):
521 elif branch in (".", "null", "tip"):
522 raise error.RevlogError(_('the name \'%s\' is reserved')
522 raise error.RevlogError(_('the name \'%s\' is reserved')
523 % branch)
523 % branch)
524 if extra:
524 if extra:
525 extra = encodeextra(extra)
525 extra = encodeextra(extra)
526 parseddate = "%s %s" % (parseddate, extra)
526 parseddate = "%s %s" % (parseddate, extra)
527 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
527 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
528 text = "\n".join(l)
528 text = "\n".join(l)
529 return self.addrevision(text, transaction, len(self), p1, p2)
529 return self.addrevision(text, transaction, len(self), p1, p2)
530
530
531 def branchinfo(self, rev):
531 def branchinfo(self, rev):
532 """return the branch name and open/close state of a revision
532 """return the branch name and open/close state of a revision
533
533
534 This function exists because creating a changectx object
534 This function exists because creating a changectx object
535 just to access this is costly."""
535 just to access this is costly."""
536 extra = self.read(rev)[5]
536 extra = self.read(rev)[5]
537 return encoding.tolocal(extra.get("branch")), 'close' in extra
537 return encoding.tolocal(extra.get("branch")), 'close' in extra
General Comments 0
You need to be logged in to leave comments. Login now