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