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