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