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