changelog.py
746 lines
| 23.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / changelog.py
|
r1095 | # changelog.py - changelog class for mercurial | ||
|
r1089 | # | ||
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
|
r1089 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r1089 | |||
|
r25922 | from __future__ import absolute_import | ||
from .i18n import _ | ||||
from .node import ( | ||||
bin, | ||||
hex, | ||||
nullid, | ||||
) | ||||
|
r43346 | from .thirdparty import attr | ||
|
r25922 | |||
from . import ( | ||||
encoding, | ||||
error, | ||||
|
r36246 | pycompat, | ||
|
r25922 | revlog, | ||
|
r41929 | util, | ||
|
r25922 | ) | ||
|
r37102 | from .utils import ( | ||
dateutil, | ||||
stringutil, | ||||
) | ||||
|
r1089 | |||
r43412 | from .revlogutils import sidedata as sidedatamod | |||
|
r43347 | _defaultextra = {b'branch': b'default'} | ||
|
r16267 | |||
|
r43346 | |||
|
r3232 | def _string_escape(text): | ||
""" | ||||
|
r34135 | >>> from .pycompat import bytechr as chr | ||
|
r34133 | >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)} | ||
|
r42285 | >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d | ||
|
r3232 | >>> s | ||
|
r42285 | 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n' | ||
|
r3232 | >>> res = _string_escape(s) | ||
|
r42285 | >>> s == _string_unescape(res) | ||
|
r3232 | True | ||
""" | ||||
# subset of the string_escape codec | ||||
|
r43347 | text = ( | ||
text.replace(b'\\', b'\\\\') | ||||
.replace(b'\n', b'\\n') | ||||
.replace(b'\r', b'\\r') | ||||
) | ||||
return text.replace(b'\0', b'\\0') | ||||
|
r3232 | |||
|
r43346 | |||
|
r42285 | def _string_unescape(text): | ||
|
r43347 | if b'\\0' in text: | ||
|
r42285 | # fix up \0 without getting into trouble with \\0 | ||
|
r43347 | text = text.replace(b'\\\\', b'\\\\\n') | ||
text = text.replace(b'\\0', b'\0') | ||||
text = text.replace(b'\n', b'') | ||||
|
r42285 | return stringutil.unescapestr(text) | ||
|
r43346 | |||
|
r8443 | def decodeextra(text): | ||
|
r15661 | """ | ||
|
r34135 | >>> from .pycompat import bytechr as chr | ||
|
r34133 | >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'}) | ||
|
r34134 | ... ).items()) | ||
|
r18379 | [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')] | ||
|
r34133 | >>> sorted(decodeextra(encodeextra({b'foo': b'bar', | ||
... b'baz': chr(92) + chr(0) + b'2'}) | ||||
|
r34134 | ... ).items()) | ||
|
r18379 | [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')] | ||
|
r15661 | """ | ||
|
r16267 | extra = _defaultextra.copy() | ||
|
r43347 | for l in text.split(b'\0'): | ||
|
r8443 | if l: | ||
|
r43347 | k, v = _string_unescape(l).split(b':', 1) | ||
|
r8443 | extra[k] = v | ||
return extra | ||||
|
r43346 | |||
|
r8443 | def encodeextra(d): | ||
# keys must be sorted to produce a deterministic changelog entry | ||||
|
r41715 | items = [ | ||
|
r43347 | _string_escape(b'%s:%s' % (k, pycompat.bytestr(d[k]))) | ||
for k in sorted(d) | ||||
|
r41715 | ] | ||
|
r43347 | return b"\0".join(items) | ||
|
r8443 | |||
|
r43346 | |||
|
r42619 | def encodecopies(files, copies): | ||
items = [] | ||||
for i, dst in enumerate(files): | ||||
if dst in copies: | ||||
|
r43347 | items.append(b'%d\0%s' % (i, copies[dst])) | ||
|
r42619 | if len(items) != len(copies): | ||
|
r43347 | raise error.ProgrammingError( | ||
b'some copy targets missing from file list' | ||||
) | ||||
return b"\n".join(items) | ||||
|
r42317 | |||
|
r43346 | |||
|
r42619 | def decodecopies(files, data): | ||
|
r42318 | try: | ||
copies = {} | ||||
|
r42756 | if not data: | ||
return copies | ||||
|
r43347 | for l in data.split(b'\n'): | ||
strindex, src = l.split(b'\0') | ||||
|
r42619 | i = int(strindex) | ||
dst = files[i] | ||||
copies[dst] = src | ||||
|
r42318 | return copies | ||
|
r42619 | except (ValueError, IndexError): | ||
|
r42318 | # Perhaps someone had chosen the same key name (e.g. "p1copies") and | ||
# used different syntax for the value. | ||||
return None | ||||
|
r43346 | |||
|
r42598 | def encodefileindices(files, subset): | ||
subset = set(subset) | ||||
indices = [] | ||||
for i, f in enumerate(files): | ||||
if f in subset: | ||||
|
r43347 | indices.append(b'%d' % i) | ||
return b'\n'.join(indices) | ||||
|
r42598 | |||
|
r43346 | |||
|
r42599 | def decodefileindices(files, data): | ||
try: | ||||
subset = [] | ||||
|
r42756 | if not data: | ||
return subset | ||||
|
r43347 | for strindex in data.split(b'\n'): | ||
|
r42599 | i = int(strindex) | ||
if i < 0 or i >= len(files): | ||||
return None | ||||
subset.append(files[i]) | ||||
return subset | ||||
except (ValueError, IndexError): | ||||
# Perhaps someone had chosen the same key name (e.g. "added") and | ||||
# used different syntax for the value. | ||||
return None | ||||
|
r43346 | |||
|
r17810 | def stripdesc(desc): | ||
"""strip trailing whitespace and leading and trailing empty lines""" | ||||
|
r43347 | return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n') | ||
|
r17810 | |||
|
r43346 | |||
|
r8778 | class appender(object): | ||
|
r7807 | '''the changelog index must be updated last on disk, so we use this class | ||
|
r4261 | to delay writes to it''' | ||
|
r43346 | |||
|
r19899 | def __init__(self, vfs, name, mode, buf): | ||
|
r4261 | self.data = buf | ||
|
r19899 | fp = vfs(name, mode) | ||
|
r4261 | self.fp = fp | ||
self.offset = fp.tell() | ||||
|
r19899 | self.size = vfs.fstat(fp).st_size | ||
|
r30596 | self._end = self.size | ||
|
r4261 | |||
def end(self): | ||||
|
r30596 | return self._end | ||
|
r43346 | |||
|
r4261 | def tell(self): | ||
return self.offset | ||||
|
r43346 | |||
|
r4261 | def flush(self): | ||
pass | ||||
|
r35981 | |||
@property | ||||
def closed(self): | ||||
return self.fp.closed | ||||
|
r4261 | def close(self): | ||
|
r4961 | self.fp.close() | ||
|
r4261 | |||
def seek(self, offset, whence=0): | ||||
'''virtual file offset spans real file and data''' | ||||
if whence == 0: | ||||
self.offset = offset | ||||
elif whence == 1: | ||||
self.offset += offset | ||||
elif whence == 2: | ||||
self.offset = self.end() + offset | ||||
if self.offset < self.size: | ||||
self.fp.seek(self.offset) | ||||
def read(self, count=-1): | ||||
'''only trick here is reads that span real file and data''' | ||||
|
r43347 | ret = b"" | ||
|
r4261 | if self.offset < self.size: | ||
s = self.fp.read(count) | ||||
ret = s | ||||
self.offset += len(s) | ||||
if count > 0: | ||||
count -= len(s) | ||||
if count != 0: | ||||
doff = self.offset - self.size | ||||
|
r43347 | self.data.insert(0, b"".join(self.data)) | ||
|
r4261 | del self.data[1:] | ||
|
r43346 | s = self.data[0][doff : doff + count] | ||
|
r4261 | self.offset += len(s) | ||
ret += s | ||||
return ret | ||||
def write(self, s): | ||||
|
r31642 | self.data.append(bytes(s)) | ||
|
r4261 | self.offset += len(s) | ||
|
r30596 | self._end += len(s) | ||
|
r4261 | |||
|
r35980 | def __enter__(self): | ||
self.fp.__enter__() | ||||
return self | ||||
def __exit__(self, *args): | ||||
return self.fp.__exit__(*args) | ||||
|
r43346 | |||
|
r23201 | def _divertopener(opener, target): | ||
"""build an opener that writes in 'target.a' instead of 'target'""" | ||||
|
r43346 | |||
|
r43347 | def _divert(name, mode=b'r', checkambig=False): | ||
|
r9166 | if name != target: | ||
return opener(name, mode) | ||||
|
r43347 | return opener(name + b".a", mode) | ||
|
r43346 | |||
|
r23201 | return _divert | ||
|
r43346 | |||
|
r23201 | def _delayopener(opener, target, buf): | ||
"""build an opener that stores chunks in 'buf' instead of 'target'""" | ||||
|
r43346 | |||
|
r43347 | def _delay(name, mode=b'r', checkambig=False): | ||
|
r23201 | if name != target: | ||
return opener(name, mode) | ||||
|
r19899 | return appender(opener, name, mode, buf) | ||
|
r43346 | |||
|
r23201 | return _delay | ||
|
r9166 | |||
|
r43346 | |||
|
r34399 | @attr.s | ||
class _changelogrevision(object): | ||||
# Extensions might modify _defaultextra, so let the constructor below pass | ||||
# it in | ||||
extra = attr.ib() | ||||
manifest = attr.ib(default=nullid) | ||||
|
r43347 | user = attr.ib(default=b'') | ||
|
r34399 | date = attr.ib(default=(0, 0)) | ||
|
r34442 | files = attr.ib(default=attr.Factory(list)) | ||
|
r42599 | filesadded = attr.ib(default=None) | ||
filesremoved = attr.ib(default=None) | ||||
|
r42487 | p1copies = attr.ib(default=None) | ||
p2copies = attr.ib(default=None) | ||||
|
r43347 | description = attr.ib(default=b'') | ||
|
r28487 | |||
|
r43346 | |||
|
r28487 | class changelogrevision(object): | ||
"""Holds results of a parsed changelog revision. | ||||
Changelog revisions consist of multiple pieces of data, including | ||||
the manifest node, user, and date. This object exposes a view into | ||||
the parsed object. | ||||
""" | ||||
__slots__ = ( | ||||
|
r41997 | r'_offsets', | ||
r'_text', | ||||
r43413 | r'_sidedata', | |||
|
r28487 | ) | ||
r43413 | def __new__(cls, text, sidedata): | |||
|
r28487 | if not text: | ||
|
r34399 | return _changelogrevision(extra=_defaultextra) | ||
|
r28487 | |||
self = super(changelogrevision, cls).__new__(cls) | ||||
# We could return here and implement the following as an __init__. | ||||
# But doing it here is equivalent and saves an extra function call. | ||||
# format used: | ||||
# nodeid\n : manifest node in ascii | ||||
# user\n : user, no \n or \r allowed | ||||
# time tz extra\n : date (time is int or float, timezone is int) | ||||
# : extra is metadata, encoded and separated by '\0' | ||||
# : older versions ignore it | ||||
# files\n\n : files modified by the cset, no \n or \r allowed | ||||
# (.*) : comment (free text, ideally utf-8) | ||||
# | ||||
# changelog v0 doesn't use extra | ||||
|
r43347 | nl1 = text.index(b'\n') | ||
nl2 = text.index(b'\n', nl1 + 1) | ||||
nl3 = text.index(b'\n', nl2 + 1) | ||||
|
r28487 | |||
|
r28493 | # The list of files may be empty. Which means nl3 is the first of the | ||
# double newline that precedes the description. | ||||
|
r43347 | if text[nl3 + 1 : nl3 + 2] == b'\n': | ||
|
r28495 | doublenl = nl3 | ||
|
r28493 | else: | ||
|
r43347 | doublenl = text.index(b'\n\n', nl3 + 1) | ||
|
r28495 | |||
self._offsets = (nl1, nl2, nl3, doublenl) | ||||
self._text = text | ||||
r43413 | self._sidedata = sidedata | |||
|
r28487 | |||
return self | ||||
|
r28489 | @property | ||
|
r28490 | def manifest(self): | ||
|
r43346 | return bin(self._text[0 : self._offsets[0]]) | ||
|
r28490 | |||
@property | ||||
|
r28491 | def user(self): | ||
|
r28495 | off = self._offsets | ||
|
r43346 | return encoding.tolocal(self._text[off[0] + 1 : off[1]]) | ||
|
r28491 | |||
@property | ||||
|
r28492 | def _rawdate(self): | ||
|
r28495 | off = self._offsets | ||
|
r43346 | dateextra = self._text[off[1] + 1 : off[2]] | ||
|
r43347 | return dateextra.split(b' ', 2)[0:2] | ||
|
r28492 | |||
@property | ||||
def _rawextra(self): | ||||
|
r28495 | off = self._offsets | ||
|
r43346 | dateextra = self._text[off[1] + 1 : off[2]] | ||
|
r43347 | fields = dateextra.split(b' ', 2) | ||
|
r28492 | if len(fields) != 3: | ||
return None | ||||
return fields[2] | ||||
@property | ||||
def date(self): | ||||
raw = self._rawdate | ||||
time = float(raw[0]) | ||||
# Various tools did silly things with the timezone. | ||||
try: | ||||
timezone = int(raw[1]) | ||||
except ValueError: | ||||
timezone = 0 | ||||
return time, timezone | ||||
@property | ||||
def extra(self): | ||||
raw = self._rawextra | ||||
if raw is None: | ||||
return _defaultextra | ||||
return decodeextra(raw) | ||||
@property | ||||
|
r28493 | def files(self): | ||
|
r28495 | off = self._offsets | ||
if off[2] == off[3]: | ||||
|
r28493 | return [] | ||
|
r43347 | return self._text[off[2] + 1 : off[3]].split(b'\n') | ||
|
r28493 | |||
@property | ||||
|
r42599 | def filesadded(self): | ||
|
r43347 | rawindices = self.extra.get(b'filesadded') | ||
|
r42599 | return rawindices and decodefileindices(self.files, rawindices) | ||
@property | ||||
def filesremoved(self): | ||||
|
r43347 | rawindices = self.extra.get(b'filesremoved') | ||
|
r42599 | return rawindices and decodefileindices(self.files, rawindices) | ||
@property | ||||
|
r42318 | def p1copies(self): | ||
|
r43347 | rawcopies = self.extra.get(b'p1copies') | ||
|
r42619 | return rawcopies and decodecopies(self.files, rawcopies) | ||
|
r42318 | |||
@property | ||||
def p2copies(self): | ||||
|
r43347 | rawcopies = self.extra.get(b'p2copies') | ||
|
r42619 | return rawcopies and decodecopies(self.files, rawcopies) | ||
|
r42318 | |||
@property | ||||
|
r28489 | def description(self): | ||
|
r43346 | return encoding.tolocal(self._text[self._offsets[3] + 2 :]) | ||
|
r28489 | |||
|
r7634 | class changelog(revlog.revlog): | ||
|
r32292 | def __init__(self, opener, trypending=False): | ||
"""Load a changelog revlog using an opener. | ||||
If ``trypending`` is true, we attempt to load the index from a | ||||
``00changelog.i.a`` file instead of the default ``00changelog.i``. | ||||
The ``00changelog.i.a`` file contains index (and possibly inline | ||||
revision) data for a transaction that hasn't been finalized yet. | ||||
It exists in a separate file to facilitate readers (such as | ||||
hooks processes) accessing data before a transaction is finalized. | ||||
""" | ||||
|
r43347 | if trypending and opener.exists(b'00changelog.i.a'): | ||
indexfile = b'00changelog.i.a' | ||||
|
r32292 | else: | ||
|
r43347 | indexfile = b'00changelog.i' | ||
|
r32292 | |||
|
r43347 | datafile = b'00changelog.d' | ||
|
r43346 | revlog.revlog.__init__( | ||
self, | ||||
opener, | ||||
indexfile, | ||||
datafile=datafile, | ||||
checkambig=True, | ||||
mmaplargeindex=True, | ||||
) | ||||
|
r32292 | |||
|
r41238 | if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1): | ||
# changelogs don't benefit from generaldelta. | ||||
|
r32316 | self.version &= ~revlog.FLAG_GENERALDELTA | ||
|
r14334 | self._generaldelta = False | ||
|
r30155 | |||
# Delta chains for changelogs tend to be very small because entries | ||||
# tend to be small and don't delta well with each. So disable delta | ||||
# chains. | ||||
|
r39268 | self._storedeltachains = False | ||
|
r30155 | |||
|
r8644 | self._realopener = opener | ||
self._delayed = False | ||||
|
r23201 | self._delaybuf = None | ||
|
r9163 | self._divert = False | ||
|
r18231 | self.filteredrevs = frozenset() | ||
|
r43347 | self._copiesstorage = opener.options.get(b'copies-storage') | ||
|
r17677 | |||
|
r35689 | def tiprev(self): | ||
|
r43346 | for i in pycompat.xrange(len(self) - 1, -2, -1): | ||
|
r35689 | if i not in self.filteredrevs: | ||
return i | ||||
|
r17677 | def tip(self): | ||
"""filtered version of revlog.tip""" | ||||
|
r35690 | return self.node(self.tiprev()) | ||
|
r17677 | |||
|
r24030 | def __contains__(self, rev): | ||
"""filtered version of revlog.__contains__""" | ||||
|
r43346 | return 0 <= rev < len(self) and rev not in self.filteredrevs | ||
|
r24030 | |||
|
r17677 | def __iter__(self): | ||
"""filtered version of revlog.__iter__""" | ||||
|
r17951 | if len(self.filteredrevs) == 0: | ||
return revlog.revlog.__iter__(self) | ||||
def filterediter(): | ||||
|
r38806 | for i in pycompat.xrange(len(self)): | ||
|
r17951 | if i not in self.filteredrevs: | ||
yield i | ||||
return filterediter() | ||||
|
r17677 | |||
def revs(self, start=0, stop=None): | ||||
"""filtered version of revlog.revs""" | ||||
for i in super(changelog, self).revs(start, stop): | ||||
if i not in self.filteredrevs: | ||||
yield i | ||||
|
r41929 | def _checknofilteredinrevs(self, revs): | ||
"""raise the appropriate error if 'revs' contains a filtered revision | ||||
This returns a version of 'revs' to be used thereafter by the caller. | ||||
In particular, if revs is an iterator, it is converted into a set. | ||||
""" | ||||
safehasattr = util.safehasattr | ||||
if safehasattr(revs, '__next__'): | ||||
# Note that inspect.isgenerator() is not true for iterators, | ||||
revs = set(revs) | ||||
filteredrevs = self.filteredrevs | ||||
if safehasattr(revs, 'first'): # smartset | ||||
offenders = revs & filteredrevs | ||||
else: | ||||
offenders = filteredrevs.intersection(revs) | ||||
for rev in offenders: | ||||
raise error.FilteredIndexError(rev) | ||||
return revs | ||||
|
r41311 | def headrevs(self, revs=None): | ||
if revs is None and self.filteredrevs: | ||||
|
r22484 | try: | ||
|
r23088 | return self.index.headrevsfiltered(self.filteredrevs) | ||
# AttributeError covers non-c-extension environments and | ||||
# old c extensions without filter handling. | ||||
except AttributeError: | ||||
|
r22484 | return self._headrevs() | ||
|
r41929 | if self.filteredrevs: | ||
revs = self._checknofilteredinrevs(revs) | ||||
|
r41311 | return super(changelog, self).headrevs(revs) | ||
|
r17677 | |||
def strip(self, *args, **kwargs): | ||||
# XXX make something better than assert | ||||
# We can't expect proper strip behavior if we are filtered. | ||||
assert not self.filteredrevs | ||||
super(changelog, self).strip(*args, **kwargs) | ||||
def rev(self, node): | ||||
"""filtered version of revlog.rev""" | ||||
r = super(changelog, self).rev(node) | ||||
if r in self.filteredrevs: | ||||
|
r43346 | raise error.FilteredLookupError( | ||
|
r43347 | hex(node), self.indexfile, _(b'filtered node') | ||
|
r43346 | ) | ||
|
r17677 | return r | ||
def node(self, rev): | ||||
"""filtered version of revlog.node""" | ||||
if rev in self.filteredrevs: | ||||
|
r23014 | raise error.FilteredIndexError(rev) | ||
|
r17677 | return super(changelog, self).node(rev) | ||
def linkrev(self, rev): | ||||
"""filtered version of revlog.linkrev""" | ||||
if rev in self.filteredrevs: | ||||
|
r23014 | raise error.FilteredIndexError(rev) | ||
|
r17677 | return super(changelog, self).linkrev(rev) | ||
def parentrevs(self, rev): | ||||
"""filtered version of revlog.parentrevs""" | ||||
if rev in self.filteredrevs: | ||||
|
r23014 | raise error.FilteredIndexError(rev) | ||
|
r17677 | return super(changelog, self).parentrevs(rev) | ||
def flags(self, rev): | ||||
"""filtered version of revlog.flags""" | ||||
if rev in self.filteredrevs: | ||||
|
r23014 | raise error.FilteredIndexError(rev) | ||
|
r17677 | return super(changelog, self).flags(rev) | ||
|
r1089 | |||
|
r23203 | def delayupdate(self, tr): | ||
|
r43347 | b"delay visibility of index updates to other readers" | ||
|
r23201 | |||
if not self._delayed: | ||||
if len(self) == 0: | ||||
self._divert = True | ||||
|
r43347 | if self._realopener.exists(self.indexfile + b'.a'): | ||
self._realopener.unlink(self.indexfile + b'.a') | ||||
|
r23201 | self.opener = _divertopener(self._realopener, self.indexfile) | ||
else: | ||||
self._delaybuf = [] | ||||
|
r43346 | self.opener = _delayopener( | ||
self._realopener, self.indexfile, self._delaybuf | ||||
) | ||||
|
r8644 | self._delayed = True | ||
|
r43347 | tr.addpending(b'cl-%i' % id(self), self._writepending) | ||
tr.addfinalize(b'cl-%i' % id(self), self._finalize) | ||||
|
r4261 | |||
|
r23205 | def _finalize(self, tr): | ||
|
r43347 | b"finalize index updates" | ||
|
r8644 | self._delayed = False | ||
|
r9165 | self.opener = self._realopener | ||
|
r4269 | # move redirected index data back into place | ||
|
r9164 | if self._divert: | ||
|
r23201 | assert not self._delaybuf | ||
|
r43347 | tmpname = self.indexfile + b".a" | ||
|
r19898 | nfile = self.opener.open(tmpname) | ||
|
r14207 | nfile.close() | ||
|
r29999 | self.opener.rename(tmpname, self.indexfile, checkambig=True) | ||
|
r4269 | elif self._delaybuf: | ||
|
r43347 | fp = self.opener(self.indexfile, b'a', checkambig=True) | ||
fp.write(b"".join(self._delaybuf)) | ||||
|
r4261 | fp.close() | ||
|
r23201 | self._delaybuf = None | ||
self._divert = False | ||||
|
r4269 | # split when we're done | ||
|
r35992 | self._enforceinlinesize(tr) | ||
|
r4261 | |||
|
r23280 | def _writepending(self, tr): | ||
|
r43347 | b"create a file containing the unfinalized state for pretxnchangegroup" | ||
|
r7787 | if self._delaybuf: | ||
# make a temporary copy of the index | ||||
fp1 = self._realopener(self.indexfile) | ||||
|
r43347 | pendingfilename = self.indexfile + b".a" | ||
|
r23292 | # register as a temp file to ensure cleanup on failure | ||
tr.registertmp(pendingfilename) | ||||
# write existing data | ||||
|
r43347 | fp2 = self._realopener(pendingfilename, b"w") | ||
|
r7787 | fp2.write(fp1.read()) | ||
# add pending data | ||||
|
r43347 | fp2.write(b"".join(self._delaybuf)) | ||
|
r7787 | fp2.close() | ||
# switch modes so finalize can simply rename | ||||
|
r23201 | self._delaybuf = None | ||
|
r9164 | self._divert = True | ||
|
r23201 | self.opener = _divertopener(self._realopener, self.indexfile) | ||
|
r7787 | |||
|
r9164 | if self._divert: | ||
|
r7787 | return True | ||
return False | ||||
|
r35992 | def _enforceinlinesize(self, tr, fp=None): | ||
|
r9165 | if not self._delayed: | ||
|
r35992 | revlog.revlog._enforceinlinesize(self, tr, fp) | ||
|
r4261 | |||
|
r5744 | def read(self, node): | ||
|
r28487 | """Obtain data from a parsed changelog revision. | ||
Returns a 6-tuple of: | ||||
|
r3233 | |||
|
r28487 | - manifest node in binary | ||
- author/user as a localstr | ||||
- date as a 2-tuple of (time, timezone) | ||||
- list of files | ||||
- commit message as a localstr | ||||
- dict of extra metadata | ||||
Unless you need to access all fields, consider calling | ||||
``changelogrevision`` instead, as it is faster for partial object | ||||
access. | ||||
|
r3077 | """ | ||
r43413 | c = changelogrevision(*self._revisiondata(node)) | |||
|
r43346 | return (c.manifest, c.user, c.date, c.files, c.description, c.extra) | ||
|
r3233 | |||
|
r28487 | def changelogrevision(self, nodeorrev): | ||
"""Obtain a ``changelogrevision`` for a node or revision.""" | ||||
r43413 | text, sidedata = self._revisiondata(nodeorrev) | |||
return changelogrevision(text, sidedata) | ||||
|
r1089 | |||
|
r27439 | def readfiles(self, node): | ||
""" | ||||
short version of read that only returns the files modified by the cset | ||||
""" | ||||
text = self.revision(node) | ||||
if not text: | ||||
return [] | ||||
|
r43347 | last = text.index(b"\n\n") | ||
l = text[:last].split(b'\n') | ||||
|
r27439 | return l[3:] | ||
|
r43346 | def add( | ||
self, | ||||
manifest, | ||||
files, | ||||
desc, | ||||
transaction, | ||||
p1, | ||||
p2, | ||||
user, | ||||
date=None, | ||||
extra=None, | ||||
p1copies=None, | ||||
p2copies=None, | ||||
filesadded=None, | ||||
filesremoved=None, | ||||
): | ||||
|
r14379 | # Convert to UTF-8 encoded bytestrings as the very first | ||
# thing: calling any method on a localstr object will turn it | ||||
# into a str object and the cached UTF-8 string is thus lost. | ||||
user, desc = encoding.fromlocal(user), encoding.fromlocal(desc) | ||||
|
r7035 | user = user.strip() | ||
|
r8424 | # An empty username or a username with a "\n" will make the | ||
# revision text contain two "\n\n" sequences -> corrupt | ||||
# repository since read cannot unpack the revision. | ||||
if not user: | ||||
|
r43347 | raise error.StorageError(_(b"empty username")) | ||
if b"\n" in user: | ||||
|
r43346 | raise error.StorageError( | ||
|
r43347 | _(b"username %r contains a newline") % pycompat.bytestr(user) | ||
|
r43346 | ) | ||
|
r8499 | |||
|
r17810 | desc = stripdesc(desc) | ||
|
r8499 | |||
|
r1195 | if date: | ||
|
r43347 | parseddate = b"%d %d" % dateutil.parsedate(date) | ||
|
r1195 | else: | ||
|
r43347 | parseddate = b"%d %d" % dateutil.makedate() | ||
|
r10417 | if extra: | ||
|
r43347 | branch = extra.get(b"branch") | ||
if branch in (b"default", b""): | ||||
del extra[b"branch"] | ||||
elif branch in (b".", b"null", b"tip"): | ||||
|
r43346 | raise error.StorageError( | ||
|
r43347 | _(b'the name \'%s\' is reserved') % branch | ||
|
r43346 | ) | ||
|
r42619 | sortedfiles = sorted(files) | ||
r43412 | sidedata = None | |||
|
r43127 | if extra is not None: | ||
|
r43347 | for name in ( | ||
b'p1copies', | ||||
b'p2copies', | ||||
b'filesadded', | ||||
b'filesremoved', | ||||
): | ||||
|
r43127 | extra.pop(name, None) | ||
r43297 | if p1copies is not None: | |||
p1copies = encodecopies(sortedfiles, p1copies) | ||||
if p2copies is not None: | ||||
p2copies = encodecopies(sortedfiles, p2copies) | ||||
if filesadded is not None: | ||||
filesadded = encodefileindices(sortedfiles, filesadded) | ||||
if filesremoved is not None: | ||||
filesremoved = encodefileindices(sortedfiles, filesremoved) | ||||
|
r43347 | if self._copiesstorage == b'extra': | ||
r43296 | extrasentries = p1copies, p2copies, filesadded, filesremoved | |||
if extra is None and any(x is not None for x in extrasentries): | ||||
extra = {} | ||||
if p1copies is not None: | ||||
|
r43347 | extra[b'p1copies'] = p1copies | ||
r43296 | if p2copies is not None: | |||
|
r43347 | extra[b'p2copies'] = p2copies | ||
r43296 | if filesadded is not None: | |||
|
r43347 | extra[b'filesadded'] = filesadded | ||
r43296 | if filesremoved is not None: | |||
|
r43347 | extra[b'filesremoved'] = filesremoved | ||
r43412 | elif self._copiesstorage == b'changeset-sidedata': | |||
sidedata = {} | ||||
if p1copies is not None: | ||||
sidedata[sidedatamod.SD_P1COPIES] = p1copies | ||||
if p2copies is not None: | ||||
sidedata[sidedatamod.SD_P2COPIES] = p2copies | ||||
if filesadded is not None: | ||||
sidedata[sidedatamod.SD_FILESADDED] = filesadded | ||||
if filesremoved is not None: | ||||
sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved | ||||
|
r42317 | |||
|
r3233 | if extra: | ||
|
r8443 | extra = encodeextra(extra) | ||
|
r43347 | parseddate = b"%s %s" % (parseddate, extra) | ||
l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc] | ||||
text = b"\n".join(l) | ||||
r43412 | return self.addrevision( | |||
text, transaction, len(self), p1, p2, sidedata=sidedata | ||||
) | ||||
|
r18306 | |||
|
r20185 | def branchinfo(self, rev): | ||
"""return the branch name and open/close state of a revision | ||||
|
r18306 | |||
|
r18308 | This function exists because creating a changectx object | ||
just to access this is costly.""" | ||||
|
r20185 | extra = self.read(rev)[5] | ||
|
r43347 | return encoding.tolocal(extra.get(b"branch")), b'close' in extra | ||
|
r39923 | |||
def _nodeduplicatecallback(self, transaction, node): | ||||
# keep track of revisions that got "re-added", eg: unbunde of know rev. | ||||
# | ||||
# We track them in a list to preserve their order from the source bundle | ||||
|
r43347 | duplicates = transaction.changes.setdefault(b'revduplicates', []) | ||
|
r39923 | duplicates.append(self.rev(node)) | ||