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