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