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