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