##// END OF EJS Templates
changelog: abort on attempt to write wdir revision...
Martin von Zweigbergk -
r33981:3939b30d default
parent child Browse files
Show More
@@ -1,543 +1,547 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 wdirid,
17 )
18 )
18
19
19 from . import (
20 from . import (
20 encoding,
21 encoding,
21 error,
22 error,
22 revlog,
23 revlog,
23 util,
24 util,
24 )
25 )
25
26
26 _defaultextra = {'branch': 'default'}
27 _defaultextra = {'branch': 'default'}
27
28
28 def _string_escape(text):
29 def _string_escape(text):
29 """
30 """
30 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
31 >>> 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
32 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
32 >>> s
33 >>> s
33 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
34 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
34 >>> res = _string_escape(s)
35 >>> res = _string_escape(s)
35 >>> s == util.unescapestr(res)
36 >>> s == util.unescapestr(res)
36 True
37 True
37 """
38 """
38 # subset of the string_escape codec
39 # subset of the string_escape codec
39 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
40 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
40 return text.replace('\0', '\\0')
41 return text.replace('\0', '\\0')
41
42
42 def decodeextra(text):
43 def decodeextra(text):
43 """
44 """
44 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
45 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
45 ... ).iteritems())
46 ... ).iteritems())
46 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
47 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
47 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
48 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
48 ... 'baz': chr(92) + chr(0) + '2'})
49 ... 'baz': chr(92) + chr(0) + '2'})
49 ... ).iteritems())
50 ... ).iteritems())
50 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
51 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
51 """
52 """
52 extra = _defaultextra.copy()
53 extra = _defaultextra.copy()
53 for l in text.split('\0'):
54 for l in text.split('\0'):
54 if l:
55 if l:
55 if '\\0' in l:
56 if '\\0' in l:
56 # fix up \0 without getting into trouble with \\0
57 # fix up \0 without getting into trouble with \\0
57 l = l.replace('\\\\', '\\\\\n')
58 l = l.replace('\\\\', '\\\\\n')
58 l = l.replace('\\0', '\0')
59 l = l.replace('\\0', '\0')
59 l = l.replace('\n', '')
60 l = l.replace('\n', '')
60 k, v = util.unescapestr(l).split(':', 1)
61 k, v = util.unescapestr(l).split(':', 1)
61 extra[k] = v
62 extra[k] = v
62 return extra
63 return extra
63
64
64 def encodeextra(d):
65 def encodeextra(d):
65 # keys must be sorted to produce a deterministic changelog entry
66 # keys must be sorted to produce a deterministic changelog entry
66 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
67 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
67 return "\0".join(items)
68 return "\0".join(items)
68
69
69 def stripdesc(desc):
70 def stripdesc(desc):
70 """strip trailing whitespace and leading and trailing empty lines"""
71 """strip trailing whitespace and leading and trailing empty lines"""
71 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
72 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
72
73
73 class appender(object):
74 class appender(object):
74 '''the changelog index must be updated last on disk, so we use this class
75 '''the changelog index must be updated last on disk, so we use this class
75 to delay writes to it'''
76 to delay writes to it'''
76 def __init__(self, vfs, name, mode, buf):
77 def __init__(self, vfs, name, mode, buf):
77 self.data = buf
78 self.data = buf
78 fp = vfs(name, mode)
79 fp = vfs(name, mode)
79 self.fp = fp
80 self.fp = fp
80 self.offset = fp.tell()
81 self.offset = fp.tell()
81 self.size = vfs.fstat(fp).st_size
82 self.size = vfs.fstat(fp).st_size
82 self._end = self.size
83 self._end = self.size
83
84
84 def end(self):
85 def end(self):
85 return self._end
86 return self._end
86 def tell(self):
87 def tell(self):
87 return self.offset
88 return self.offset
88 def flush(self):
89 def flush(self):
89 pass
90 pass
90 def close(self):
91 def close(self):
91 self.fp.close()
92 self.fp.close()
92
93
93 def seek(self, offset, whence=0):
94 def seek(self, offset, whence=0):
94 '''virtual file offset spans real file and data'''
95 '''virtual file offset spans real file and data'''
95 if whence == 0:
96 if whence == 0:
96 self.offset = offset
97 self.offset = offset
97 elif whence == 1:
98 elif whence == 1:
98 self.offset += offset
99 self.offset += offset
99 elif whence == 2:
100 elif whence == 2:
100 self.offset = self.end() + offset
101 self.offset = self.end() + offset
101 if self.offset < self.size:
102 if self.offset < self.size:
102 self.fp.seek(self.offset)
103 self.fp.seek(self.offset)
103
104
104 def read(self, count=-1):
105 def read(self, count=-1):
105 '''only trick here is reads that span real file and data'''
106 '''only trick here is reads that span real file and data'''
106 ret = ""
107 ret = ""
107 if self.offset < self.size:
108 if self.offset < self.size:
108 s = self.fp.read(count)
109 s = self.fp.read(count)
109 ret = s
110 ret = s
110 self.offset += len(s)
111 self.offset += len(s)
111 if count > 0:
112 if count > 0:
112 count -= len(s)
113 count -= len(s)
113 if count != 0:
114 if count != 0:
114 doff = self.offset - self.size
115 doff = self.offset - self.size
115 self.data.insert(0, "".join(self.data))
116 self.data.insert(0, "".join(self.data))
116 del self.data[1:]
117 del self.data[1:]
117 s = self.data[0][doff:doff + count]
118 s = self.data[0][doff:doff + count]
118 self.offset += len(s)
119 self.offset += len(s)
119 ret += s
120 ret += s
120 return ret
121 return ret
121
122
122 def write(self, s):
123 def write(self, s):
123 self.data.append(bytes(s))
124 self.data.append(bytes(s))
124 self.offset += len(s)
125 self.offset += len(s)
125 self._end += len(s)
126 self._end += len(s)
126
127
127 def _divertopener(opener, target):
128 def _divertopener(opener, target):
128 """build an opener that writes in 'target.a' instead of 'target'"""
129 """build an opener that writes in 'target.a' instead of 'target'"""
129 def _divert(name, mode='r', checkambig=False):
130 def _divert(name, mode='r', checkambig=False):
130 if name != target:
131 if name != target:
131 return opener(name, mode)
132 return opener(name, mode)
132 return opener(name + ".a", mode)
133 return opener(name + ".a", mode)
133 return _divert
134 return _divert
134
135
135 def _delayopener(opener, target, buf):
136 def _delayopener(opener, target, buf):
136 """build an opener that stores chunks in 'buf' instead of 'target'"""
137 """build an opener that stores chunks in 'buf' instead of 'target'"""
137 def _delay(name, mode='r', checkambig=False):
138 def _delay(name, mode='r', checkambig=False):
138 if name != target:
139 if name != target:
139 return opener(name, mode)
140 return opener(name, mode)
140 return appender(opener, name, mode, buf)
141 return appender(opener, name, mode, buf)
141 return _delay
142 return _delay
142
143
143 _changelogrevision = collections.namedtuple(u'changelogrevision',
144 _changelogrevision = collections.namedtuple(u'changelogrevision',
144 (u'manifest', u'user', u'date',
145 (u'manifest', u'user', u'date',
145 u'files', u'description',
146 u'files', u'description',
146 u'extra'))
147 u'extra'))
147
148
148 class changelogrevision(object):
149 class changelogrevision(object):
149 """Holds results of a parsed changelog revision.
150 """Holds results of a parsed changelog revision.
150
151
151 Changelog revisions consist of multiple pieces of data, including
152 Changelog revisions consist of multiple pieces of data, including
152 the manifest node, user, and date. This object exposes a view into
153 the manifest node, user, and date. This object exposes a view into
153 the parsed object.
154 the parsed object.
154 """
155 """
155
156
156 __slots__ = (
157 __slots__ = (
157 u'_offsets',
158 u'_offsets',
158 u'_text',
159 u'_text',
159 )
160 )
160
161
161 def __new__(cls, text):
162 def __new__(cls, text):
162 if not text:
163 if not text:
163 return _changelogrevision(
164 return _changelogrevision(
164 manifest=nullid,
165 manifest=nullid,
165 user='',
166 user='',
166 date=(0, 0),
167 date=(0, 0),
167 files=[],
168 files=[],
168 description='',
169 description='',
169 extra=_defaultextra,
170 extra=_defaultextra,
170 )
171 )
171
172
172 self = super(changelogrevision, cls).__new__(cls)
173 self = super(changelogrevision, cls).__new__(cls)
173 # We could return here and implement the following as an __init__.
174 # We could return here and implement the following as an __init__.
174 # But doing it here is equivalent and saves an extra function call.
175 # But doing it here is equivalent and saves an extra function call.
175
176
176 # format used:
177 # format used:
177 # nodeid\n : manifest node in ascii
178 # nodeid\n : manifest node in ascii
178 # user\n : user, no \n or \r allowed
179 # user\n : user, no \n or \r allowed
179 # time tz extra\n : date (time is int or float, timezone is int)
180 # time tz extra\n : date (time is int or float, timezone is int)
180 # : extra is metadata, encoded and separated by '\0'
181 # : extra is metadata, encoded and separated by '\0'
181 # : older versions ignore it
182 # : older versions ignore it
182 # files\n\n : files modified by the cset, no \n or \r allowed
183 # files\n\n : files modified by the cset, no \n or \r allowed
183 # (.*) : comment (free text, ideally utf-8)
184 # (.*) : comment (free text, ideally utf-8)
184 #
185 #
185 # changelog v0 doesn't use extra
186 # changelog v0 doesn't use extra
186
187
187 nl1 = text.index('\n')
188 nl1 = text.index('\n')
188 nl2 = text.index('\n', nl1 + 1)
189 nl2 = text.index('\n', nl1 + 1)
189 nl3 = text.index('\n', nl2 + 1)
190 nl3 = text.index('\n', nl2 + 1)
190
191
191 # The list of files may be empty. Which means nl3 is the first of the
192 # The list of files may be empty. Which means nl3 is the first of the
192 # double newline that precedes the description.
193 # double newline that precedes the description.
193 if text[nl3 + 1:nl3 + 2] == '\n':
194 if text[nl3 + 1:nl3 + 2] == '\n':
194 doublenl = nl3
195 doublenl = nl3
195 else:
196 else:
196 doublenl = text.index('\n\n', nl3 + 1)
197 doublenl = text.index('\n\n', nl3 + 1)
197
198
198 self._offsets = (nl1, nl2, nl3, doublenl)
199 self._offsets = (nl1, nl2, nl3, doublenl)
199 self._text = text
200 self._text = text
200
201
201 return self
202 return self
202
203
203 @property
204 @property
204 def manifest(self):
205 def manifest(self):
205 return bin(self._text[0:self._offsets[0]])
206 return bin(self._text[0:self._offsets[0]])
206
207
207 @property
208 @property
208 def user(self):
209 def user(self):
209 off = self._offsets
210 off = self._offsets
210 return encoding.tolocal(self._text[off[0] + 1:off[1]])
211 return encoding.tolocal(self._text[off[0] + 1:off[1]])
211
212
212 @property
213 @property
213 def _rawdate(self):
214 def _rawdate(self):
214 off = self._offsets
215 off = self._offsets
215 dateextra = self._text[off[1] + 1:off[2]]
216 dateextra = self._text[off[1] + 1:off[2]]
216 return dateextra.split(' ', 2)[0:2]
217 return dateextra.split(' ', 2)[0:2]
217
218
218 @property
219 @property
219 def _rawextra(self):
220 def _rawextra(self):
220 off = self._offsets
221 off = self._offsets
221 dateextra = self._text[off[1] + 1:off[2]]
222 dateextra = self._text[off[1] + 1:off[2]]
222 fields = dateextra.split(' ', 2)
223 fields = dateextra.split(' ', 2)
223 if len(fields) != 3:
224 if len(fields) != 3:
224 return None
225 return None
225
226
226 return fields[2]
227 return fields[2]
227
228
228 @property
229 @property
229 def date(self):
230 def date(self):
230 raw = self._rawdate
231 raw = self._rawdate
231 time = float(raw[0])
232 time = float(raw[0])
232 # Various tools did silly things with the timezone.
233 # Various tools did silly things with the timezone.
233 try:
234 try:
234 timezone = int(raw[1])
235 timezone = int(raw[1])
235 except ValueError:
236 except ValueError:
236 timezone = 0
237 timezone = 0
237
238
238 return time, timezone
239 return time, timezone
239
240
240 @property
241 @property
241 def extra(self):
242 def extra(self):
242 raw = self._rawextra
243 raw = self._rawextra
243 if raw is None:
244 if raw is None:
244 return _defaultextra
245 return _defaultextra
245
246
246 return decodeextra(raw)
247 return decodeextra(raw)
247
248
248 @property
249 @property
249 def files(self):
250 def files(self):
250 off = self._offsets
251 off = self._offsets
251 if off[2] == off[3]:
252 if off[2] == off[3]:
252 return []
253 return []
253
254
254 return self._text[off[2] + 1:off[3]].split('\n')
255 return self._text[off[2] + 1:off[3]].split('\n')
255
256
256 @property
257 @property
257 def description(self):
258 def description(self):
258 return encoding.tolocal(self._text[self._offsets[3] + 2:])
259 return encoding.tolocal(self._text[self._offsets[3] + 2:])
259
260
260 class changelog(revlog.revlog):
261 class changelog(revlog.revlog):
261 def __init__(self, opener, trypending=False):
262 def __init__(self, opener, trypending=False):
262 """Load a changelog revlog using an opener.
263 """Load a changelog revlog using an opener.
263
264
264 If ``trypending`` is true, we attempt to load the index from a
265 If ``trypending`` is true, we attempt to load the index from a
265 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
266 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
266 The ``00changelog.i.a`` file contains index (and possibly inline
267 The ``00changelog.i.a`` file contains index (and possibly inline
267 revision) data for a transaction that hasn't been finalized yet.
268 revision) data for a transaction that hasn't been finalized yet.
268 It exists in a separate file to facilitate readers (such as
269 It exists in a separate file to facilitate readers (such as
269 hooks processes) accessing data before a transaction is finalized.
270 hooks processes) accessing data before a transaction is finalized.
270 """
271 """
271 if trypending and opener.exists('00changelog.i.a'):
272 if trypending and opener.exists('00changelog.i.a'):
272 indexfile = '00changelog.i.a'
273 indexfile = '00changelog.i.a'
273 else:
274 else:
274 indexfile = '00changelog.i'
275 indexfile = '00changelog.i'
275
276
276 datafile = '00changelog.d'
277 datafile = '00changelog.d'
277 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
278 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
278 checkambig=True)
279 checkambig=True)
279
280
280 if self._initempty:
281 if self._initempty:
281 # changelogs don't benefit from generaldelta
282 # changelogs don't benefit from generaldelta
282 self.version &= ~revlog.FLAG_GENERALDELTA
283 self.version &= ~revlog.FLAG_GENERALDELTA
283 self._generaldelta = False
284 self._generaldelta = False
284
285
285 # Delta chains for changelogs tend to be very small because entries
286 # Delta chains for changelogs tend to be very small because entries
286 # tend to be small and don't delta well with each. So disable delta
287 # tend to be small and don't delta well with each. So disable delta
287 # chains.
288 # chains.
288 self.storedeltachains = False
289 self.storedeltachains = False
289
290
290 self._realopener = opener
291 self._realopener = opener
291 self._delayed = False
292 self._delayed = False
292 self._delaybuf = None
293 self._delaybuf = None
293 self._divert = False
294 self._divert = False
294 self.filteredrevs = frozenset()
295 self.filteredrevs = frozenset()
295
296
296 def tip(self):
297 def tip(self):
297 """filtered version of revlog.tip"""
298 """filtered version of revlog.tip"""
298 for i in xrange(len(self) -1, -2, -1):
299 for i in xrange(len(self) -1, -2, -1):
299 if i not in self.filteredrevs:
300 if i not in self.filteredrevs:
300 return self.node(i)
301 return self.node(i)
301
302
302 def __contains__(self, rev):
303 def __contains__(self, rev):
303 """filtered version of revlog.__contains__"""
304 """filtered version of revlog.__contains__"""
304 return (0 <= rev < len(self)
305 return (0 <= rev < len(self)
305 and rev not in self.filteredrevs)
306 and rev not in self.filteredrevs)
306
307
307 def __iter__(self):
308 def __iter__(self):
308 """filtered version of revlog.__iter__"""
309 """filtered version of revlog.__iter__"""
309 if len(self.filteredrevs) == 0:
310 if len(self.filteredrevs) == 0:
310 return revlog.revlog.__iter__(self)
311 return revlog.revlog.__iter__(self)
311
312
312 def filterediter():
313 def filterediter():
313 for i in xrange(len(self)):
314 for i in xrange(len(self)):
314 if i not in self.filteredrevs:
315 if i not in self.filteredrevs:
315 yield i
316 yield i
316
317
317 return filterediter()
318 return filterediter()
318
319
319 def revs(self, start=0, stop=None):
320 def revs(self, start=0, stop=None):
320 """filtered version of revlog.revs"""
321 """filtered version of revlog.revs"""
321 for i in super(changelog, self).revs(start, stop):
322 for i in super(changelog, self).revs(start, stop):
322 if i not in self.filteredrevs:
323 if i not in self.filteredrevs:
323 yield i
324 yield i
324
325
325 @util.propertycache
326 @util.propertycache
326 def nodemap(self):
327 def nodemap(self):
327 # XXX need filtering too
328 # XXX need filtering too
328 self.rev(self.node(0))
329 self.rev(self.node(0))
329 return self._nodecache
330 return self._nodecache
330
331
331 def reachableroots(self, minroot, heads, roots, includepath=False):
332 def reachableroots(self, minroot, heads, roots, includepath=False):
332 return self.index.reachableroots2(minroot, heads, roots, includepath)
333 return self.index.reachableroots2(minroot, heads, roots, includepath)
333
334
334 def headrevs(self):
335 def headrevs(self):
335 if self.filteredrevs:
336 if self.filteredrevs:
336 try:
337 try:
337 return self.index.headrevsfiltered(self.filteredrevs)
338 return self.index.headrevsfiltered(self.filteredrevs)
338 # AttributeError covers non-c-extension environments and
339 # AttributeError covers non-c-extension environments and
339 # old c extensions without filter handling.
340 # old c extensions without filter handling.
340 except AttributeError:
341 except AttributeError:
341 return self._headrevs()
342 return self._headrevs()
342
343
343 return super(changelog, self).headrevs()
344 return super(changelog, self).headrevs()
344
345
345 def strip(self, *args, **kwargs):
346 def strip(self, *args, **kwargs):
346 # XXX make something better than assert
347 # XXX make something better than assert
347 # We can't expect proper strip behavior if we are filtered.
348 # We can't expect proper strip behavior if we are filtered.
348 assert not self.filteredrevs
349 assert not self.filteredrevs
349 super(changelog, self).strip(*args, **kwargs)
350 super(changelog, self).strip(*args, **kwargs)
350
351
351 def rev(self, node):
352 def rev(self, node):
352 """filtered version of revlog.rev"""
353 """filtered version of revlog.rev"""
353 r = super(changelog, self).rev(node)
354 r = super(changelog, self).rev(node)
354 if r in self.filteredrevs:
355 if r in self.filteredrevs:
355 raise error.FilteredLookupError(hex(node), self.indexfile,
356 raise error.FilteredLookupError(hex(node), self.indexfile,
356 _('filtered node'))
357 _('filtered node'))
357 return r
358 return r
358
359
359 def node(self, rev):
360 def node(self, rev):
360 """filtered version of revlog.node"""
361 """filtered version of revlog.node"""
361 if rev in self.filteredrevs:
362 if rev in self.filteredrevs:
362 raise error.FilteredIndexError(rev)
363 raise error.FilteredIndexError(rev)
363 return super(changelog, self).node(rev)
364 return super(changelog, self).node(rev)
364
365
365 def linkrev(self, rev):
366 def linkrev(self, rev):
366 """filtered version of revlog.linkrev"""
367 """filtered version of revlog.linkrev"""
367 if rev in self.filteredrevs:
368 if rev in self.filteredrevs:
368 raise error.FilteredIndexError(rev)
369 raise error.FilteredIndexError(rev)
369 return super(changelog, self).linkrev(rev)
370 return super(changelog, self).linkrev(rev)
370
371
371 def parentrevs(self, rev):
372 def parentrevs(self, rev):
372 """filtered version of revlog.parentrevs"""
373 """filtered version of revlog.parentrevs"""
373 if rev in self.filteredrevs:
374 if rev in self.filteredrevs:
374 raise error.FilteredIndexError(rev)
375 raise error.FilteredIndexError(rev)
375 return super(changelog, self).parentrevs(rev)
376 return super(changelog, self).parentrevs(rev)
376
377
377 def flags(self, rev):
378 def flags(self, rev):
378 """filtered version of revlog.flags"""
379 """filtered version of revlog.flags"""
379 if rev in self.filteredrevs:
380 if rev in self.filteredrevs:
380 raise error.FilteredIndexError(rev)
381 raise error.FilteredIndexError(rev)
381 return super(changelog, self).flags(rev)
382 return super(changelog, self).flags(rev)
382
383
383 def delayupdate(self, tr):
384 def delayupdate(self, tr):
384 "delay visibility of index updates to other readers"
385 "delay visibility of index updates to other readers"
385
386
386 if not self._delayed:
387 if not self._delayed:
387 if len(self) == 0:
388 if len(self) == 0:
388 self._divert = True
389 self._divert = True
389 if self._realopener.exists(self.indexfile + '.a'):
390 if self._realopener.exists(self.indexfile + '.a'):
390 self._realopener.unlink(self.indexfile + '.a')
391 self._realopener.unlink(self.indexfile + '.a')
391 self.opener = _divertopener(self._realopener, self.indexfile)
392 self.opener = _divertopener(self._realopener, self.indexfile)
392 else:
393 else:
393 self._delaybuf = []
394 self._delaybuf = []
394 self.opener = _delayopener(self._realopener, self.indexfile,
395 self.opener = _delayopener(self._realopener, self.indexfile,
395 self._delaybuf)
396 self._delaybuf)
396 self._delayed = True
397 self._delayed = True
397 tr.addpending('cl-%i' % id(self), self._writepending)
398 tr.addpending('cl-%i' % id(self), self._writepending)
398 tr.addfinalize('cl-%i' % id(self), self._finalize)
399 tr.addfinalize('cl-%i' % id(self), self._finalize)
399
400
400 def _finalize(self, tr):
401 def _finalize(self, tr):
401 "finalize index updates"
402 "finalize index updates"
402 self._delayed = False
403 self._delayed = False
403 self.opener = self._realopener
404 self.opener = self._realopener
404 # move redirected index data back into place
405 # move redirected index data back into place
405 if self._divert:
406 if self._divert:
406 assert not self._delaybuf
407 assert not self._delaybuf
407 tmpname = self.indexfile + ".a"
408 tmpname = self.indexfile + ".a"
408 nfile = self.opener.open(tmpname)
409 nfile = self.opener.open(tmpname)
409 nfile.close()
410 nfile.close()
410 self.opener.rename(tmpname, self.indexfile, checkambig=True)
411 self.opener.rename(tmpname, self.indexfile, checkambig=True)
411 elif self._delaybuf:
412 elif self._delaybuf:
412 fp = self.opener(self.indexfile, 'a', checkambig=True)
413 fp = self.opener(self.indexfile, 'a', checkambig=True)
413 fp.write("".join(self._delaybuf))
414 fp.write("".join(self._delaybuf))
414 fp.close()
415 fp.close()
415 self._delaybuf = None
416 self._delaybuf = None
416 self._divert = False
417 self._divert = False
417 # split when we're done
418 # split when we're done
418 self.checkinlinesize(tr)
419 self.checkinlinesize(tr)
419
420
420 def _writepending(self, tr):
421 def _writepending(self, tr):
421 "create a file containing the unfinalized state for pretxnchangegroup"
422 "create a file containing the unfinalized state for pretxnchangegroup"
422 if self._delaybuf:
423 if self._delaybuf:
423 # make a temporary copy of the index
424 # make a temporary copy of the index
424 fp1 = self._realopener(self.indexfile)
425 fp1 = self._realopener(self.indexfile)
425 pendingfilename = self.indexfile + ".a"
426 pendingfilename = self.indexfile + ".a"
426 # register as a temp file to ensure cleanup on failure
427 # register as a temp file to ensure cleanup on failure
427 tr.registertmp(pendingfilename)
428 tr.registertmp(pendingfilename)
428 # write existing data
429 # write existing data
429 fp2 = self._realopener(pendingfilename, "w")
430 fp2 = self._realopener(pendingfilename, "w")
430 fp2.write(fp1.read())
431 fp2.write(fp1.read())
431 # add pending data
432 # add pending data
432 fp2.write("".join(self._delaybuf))
433 fp2.write("".join(self._delaybuf))
433 fp2.close()
434 fp2.close()
434 # switch modes so finalize can simply rename
435 # switch modes so finalize can simply rename
435 self._delaybuf = None
436 self._delaybuf = None
436 self._divert = True
437 self._divert = True
437 self.opener = _divertopener(self._realopener, self.indexfile)
438 self.opener = _divertopener(self._realopener, self.indexfile)
438
439
439 if self._divert:
440 if self._divert:
440 return True
441 return True
441
442
442 return False
443 return False
443
444
444 def checkinlinesize(self, tr, fp=None):
445 def checkinlinesize(self, tr, fp=None):
445 if not self._delayed:
446 if not self._delayed:
446 revlog.revlog.checkinlinesize(self, tr, fp)
447 revlog.revlog.checkinlinesize(self, tr, fp)
447
448
448 def read(self, node):
449 def read(self, node):
449 """Obtain data from a parsed changelog revision.
450 """Obtain data from a parsed changelog revision.
450
451
451 Returns a 6-tuple of:
452 Returns a 6-tuple of:
452
453
453 - manifest node in binary
454 - manifest node in binary
454 - author/user as a localstr
455 - author/user as a localstr
455 - date as a 2-tuple of (time, timezone)
456 - date as a 2-tuple of (time, timezone)
456 - list of files
457 - list of files
457 - commit message as a localstr
458 - commit message as a localstr
458 - dict of extra metadata
459 - dict of extra metadata
459
460
460 Unless you need to access all fields, consider calling
461 Unless you need to access all fields, consider calling
461 ``changelogrevision`` instead, as it is faster for partial object
462 ``changelogrevision`` instead, as it is faster for partial object
462 access.
463 access.
463 """
464 """
464 c = changelogrevision(self.revision(node))
465 c = changelogrevision(self.revision(node))
465 return (
466 return (
466 c.manifest,
467 c.manifest,
467 c.user,
468 c.user,
468 c.date,
469 c.date,
469 c.files,
470 c.files,
470 c.description,
471 c.description,
471 c.extra
472 c.extra
472 )
473 )
473
474
474 def changelogrevision(self, nodeorrev):
475 def changelogrevision(self, nodeorrev):
475 """Obtain a ``changelogrevision`` for a node or revision."""
476 """Obtain a ``changelogrevision`` for a node or revision."""
476 return changelogrevision(self.revision(nodeorrev))
477 return changelogrevision(self.revision(nodeorrev))
477
478
478 def readfiles(self, node):
479 def readfiles(self, node):
479 """
480 """
480 short version of read that only returns the files modified by the cset
481 short version of read that only returns the files modified by the cset
481 """
482 """
482 text = self.revision(node)
483 text = self.revision(node)
483 if not text:
484 if not text:
484 return []
485 return []
485 last = text.index("\n\n")
486 last = text.index("\n\n")
486 l = text[:last].split('\n')
487 l = text[:last].split('\n')
487 return l[3:]
488 return l[3:]
488
489
489 def add(self, manifest, files, desc, transaction, p1, p2,
490 def add(self, manifest, files, desc, transaction, p1, p2,
490 user, date=None, extra=None):
491 user, date=None, extra=None):
491 # Convert to UTF-8 encoded bytestrings as the very first
492 # Convert to UTF-8 encoded bytestrings as the very first
492 # thing: calling any method on a localstr object will turn it
493 # thing: calling any method on a localstr object will turn it
493 # into a str object and the cached UTF-8 string is thus lost.
494 # into a str object and the cached UTF-8 string is thus lost.
494 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
495 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
495
496
496 user = user.strip()
497 user = user.strip()
497 # An empty username or a username with a "\n" will make the
498 # An empty username or a username with a "\n" will make the
498 # revision text contain two "\n\n" sequences -> corrupt
499 # revision text contain two "\n\n" sequences -> corrupt
499 # repository since read cannot unpack the revision.
500 # repository since read cannot unpack the revision.
500 if not user:
501 if not user:
501 raise error.RevlogError(_("empty username"))
502 raise error.RevlogError(_("empty username"))
502 if "\n" in user:
503 if "\n" in user:
503 raise error.RevlogError(_("username %s contains a newline")
504 raise error.RevlogError(_("username %s contains a newline")
504 % repr(user))
505 % repr(user))
505
506
506 desc = stripdesc(desc)
507 desc = stripdesc(desc)
507
508
508 if date:
509 if date:
509 parseddate = "%d %d" % util.parsedate(date)
510 parseddate = "%d %d" % util.parsedate(date)
510 else:
511 else:
511 parseddate = "%d %d" % util.makedate()
512 parseddate = "%d %d" % util.makedate()
512 if extra:
513 if extra:
513 branch = extra.get("branch")
514 branch = extra.get("branch")
514 if branch in ("default", ""):
515 if branch in ("default", ""):
515 del extra["branch"]
516 del extra["branch"]
516 elif branch in (".", "null", "tip"):
517 elif branch in (".", "null", "tip"):
517 raise error.RevlogError(_('the name \'%s\' is reserved')
518 raise error.RevlogError(_('the name \'%s\' is reserved')
518 % branch)
519 % branch)
519 if extra:
520 if extra:
520 extra = encodeextra(extra)
521 extra = encodeextra(extra)
521 parseddate = "%s %s" % (parseddate, extra)
522 parseddate = "%s %s" % (parseddate, extra)
522 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
523 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
523 text = "\n".join(l)
524 text = "\n".join(l)
524 return self.addrevision(text, transaction, len(self), p1, p2)
525 return self.addrevision(text, transaction, len(self), p1, p2)
525
526
526 def branchinfo(self, rev):
527 def branchinfo(self, rev):
527 """return the branch name and open/close state of a revision
528 """return the branch name and open/close state of a revision
528
529
529 This function exists because creating a changectx object
530 This function exists because creating a changectx object
530 just to access this is costly."""
531 just to access this is costly."""
531 extra = self.read(rev)[5]
532 extra = self.read(rev)[5]
532 return encoding.tolocal(extra.get("branch")), 'close' in extra
533 return encoding.tolocal(extra.get("branch")), 'close' in extra
533
534
534 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
535 def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
535 # overlay over the standard revlog._addrevision to track the new
536 # overlay over the standard revlog._addrevision to track the new
536 # revision on the transaction.
537 # revision on the transaction.
537 rev = len(self)
538 rev = len(self)
539 if node == wdirid: # nullid is checked in super method
540 raise error.RevlogError(_("%s: attempt to add wdir revision") %
541 (self.indexfile))
538 node = super(changelog, self)._addrevision(node, rawtext, transaction,
542 node = super(changelog, self)._addrevision(node, rawtext, transaction,
539 *args, **kwargs)
543 *args, **kwargs)
540 revs = transaction.changes.get('revs')
544 revs = transaction.changes.get('revs')
541 if revs is not None:
545 if revs is not None:
542 revs.add(rev)
546 revs.add(rev)
543 return node
547 return node
General Comments 0
You need to be logged in to leave comments. Login now