changelog.py
672 lines
| 21.9 KiB
| text/x-python
|
PythonLexer
/ mercurial / changelog.py
mpm@selenic.com
|
r1095 | # changelog.py - changelog class for mercurial | ||
mpm@selenic.com
|
r1089 | # | ||
Thomas Arendsen Hein
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
mpm@selenic.com
|
r1089 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
mpm@selenic.com
|
r1089 | |||
Gregory Szorc
|
r25922 | from __future__ import absolute_import | ||
from .i18n import _ | ||||
from .node import ( | ||||
bin, | ||||
hex, | ||||
nullid, | ||||
) | ||||
Siddharth Agarwal
|
r34399 | from .thirdparty import ( | ||
attr, | ||||
) | ||||
Gregory Szorc
|
r25922 | |||
from . import ( | ||||
encoding, | ||||
error, | ||||
Pulkit Goyal
|
r36246 | pycompat, | ||
Gregory Szorc
|
r25922 | revlog, | ||
Georges Racinet
|
r41929 | util, | ||
Gregory Szorc
|
r25922 | ) | ||
Yuya Nishihara
|
r37102 | from .utils import ( | ||
dateutil, | ||||
stringutil, | ||||
) | ||||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r16267 | _defaultextra = {'branch': 'default'} | ||
Benoit Boissinot
|
r3232 | def _string_escape(text): | ||
""" | ||||
Yuya Nishihara
|
r34135 | >>> from .pycompat import bytechr as chr | ||
Yuya Nishihara
|
r34133 | >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)} | ||
Martin von Zweigbergk
|
r42285 | >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d | ||
Benoit Boissinot
|
r3232 | >>> s | ||
Martin von Zweigbergk
|
r42285 | 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n' | ||
Benoit Boissinot
|
r3232 | >>> res = _string_escape(s) | ||
Martin von Zweigbergk
|
r42285 | >>> s == _string_unescape(res) | ||
Benoit Boissinot
|
r3232 | True | ||
""" | ||||
# subset of the string_escape codec | ||||
text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r') | ||||
return text.replace('\0', '\\0') | ||||
Martin von Zweigbergk
|
r42285 | def _string_unescape(text): | ||
if '\\0' in text: | ||||
# fix up \0 without getting into trouble with \\0 | ||||
text = text.replace('\\\\', '\\\\\n') | ||||
text = text.replace('\\0', '\0') | ||||
text = text.replace('\n', '') | ||||
return stringutil.unescapestr(text) | ||||
Martin Geisler
|
r8443 | def decodeextra(text): | ||
Matt Mackall
|
r15661 | """ | ||
Yuya Nishihara
|
r34135 | >>> from .pycompat import bytechr as chr | ||
Yuya Nishihara
|
r34133 | >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'}) | ||
Yuya Nishihara
|
r34134 | ... ).items()) | ||
Mads Kiilerich
|
r18379 | [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')] | ||
Yuya Nishihara
|
r34133 | >>> sorted(decodeextra(encodeextra({b'foo': b'bar', | ||
... b'baz': chr(92) + chr(0) + b'2'}) | ||||
Yuya Nishihara
|
r34134 | ... ).items()) | ||
Mads Kiilerich
|
r18379 | [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')] | ||
Matt Mackall
|
r15661 | """ | ||
Matt Mackall
|
r16267 | extra = _defaultextra.copy() | ||
Martin Geisler
|
r8443 | for l in text.split('\0'): | ||
if l: | ||||
Martin von Zweigbergk
|
r42285 | k, v = _string_unescape(l).split(':', 1) | ||
Martin Geisler
|
r8443 | extra[k] = v | ||
return extra | ||||
def encodeextra(d): | ||||
# keys must be sorted to produce a deterministic changelog entry | ||||
Pulkit Goyal
|
r41715 | items = [ | ||
_string_escape('%s:%s' % (k, pycompat.bytestr(d[k]))) | ||||
for k in sorted(d) | ||||
] | ||||
Martin Geisler
|
r8443 | return "\0".join(items) | ||
Martin von Zweigbergk
|
r42619 | def encodecopies(files, copies): | ||
items = [] | ||||
for i, dst in enumerate(files): | ||||
if dst in copies: | ||||
items.append('%d\0%s' % (i, copies[dst])) | ||||
if len(items) != len(copies): | ||||
raise error.ProgrammingError('some copy targets missing from file list') | ||||
Martin von Zweigbergk
|
r42317 | return "\n".join(items) | ||
Martin von Zweigbergk
|
r42619 | def decodecopies(files, data): | ||
Martin von Zweigbergk
|
r42318 | try: | ||
copies = {} | ||||
Martin von Zweigbergk
|
r42756 | if not data: | ||
return copies | ||||
Martin von Zweigbergk
|
r42318 | for l in data.split('\n'): | ||
Martin von Zweigbergk
|
r42619 | strindex, src = l.split('\0') | ||
i = int(strindex) | ||||
dst = files[i] | ||||
copies[dst] = src | ||||
Martin von Zweigbergk
|
r42318 | return copies | ||
Martin von Zweigbergk
|
r42619 | except (ValueError, IndexError): | ||
Martin von Zweigbergk
|
r42318 | # Perhaps someone had chosen the same key name (e.g. "p1copies") and | ||
# used different syntax for the value. | ||||
return None | ||||
Martin von Zweigbergk
|
r42598 | def encodefileindices(files, subset): | ||
subset = set(subset) | ||||
indices = [] | ||||
for i, f in enumerate(files): | ||||
if f in subset: | ||||
indices.append('%d' % i) | ||||
Martin von Zweigbergk
|
r42620 | return '\n'.join(indices) | ||
Martin von Zweigbergk
|
r42598 | |||
Martin von Zweigbergk
|
r42599 | def decodefileindices(files, data): | ||
try: | ||||
subset = [] | ||||
Martin von Zweigbergk
|
r42756 | if not data: | ||
return subset | ||||
Martin von Zweigbergk
|
r42620 | for strindex in data.split('\n'): | ||
Martin von Zweigbergk
|
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 | ||||
Pierre-Yves David
|
r17810 | def stripdesc(desc): | ||
"""strip trailing whitespace and leading and trailing empty lines""" | ||||
return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n') | ||||
Benoit Boissinot
|
r8778 | class appender(object): | ||
timeless
|
r7807 | '''the changelog index must be updated last on disk, so we use this class | ||
Matt Mackall
|
r4261 | to delay writes to it''' | ||
FUJIWARA Katsunori
|
r19899 | def __init__(self, vfs, name, mode, buf): | ||
Matt Mackall
|
r4261 | self.data = buf | ||
FUJIWARA Katsunori
|
r19899 | fp = vfs(name, mode) | ||
Matt Mackall
|
r4261 | self.fp = fp | ||
self.offset = fp.tell() | ||||
FUJIWARA Katsunori
|
r19899 | self.size = vfs.fstat(fp).st_size | ||
Durham Goode
|
r30596 | self._end = self.size | ||
Matt Mackall
|
r4261 | |||
def end(self): | ||||
Durham Goode
|
r30596 | return self._end | ||
Matt Mackall
|
r4261 | def tell(self): | ||
return self.offset | ||||
def flush(self): | ||||
pass | ||||
Boris Feld
|
r35981 | |||
@property | ||||
def closed(self): | ||||
return self.fp.closed | ||||
Matt Mackall
|
r4261 | def close(self): | ||
Benoit Boissinot
|
r4961 | self.fp.close() | ||
Matt Mackall
|
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''' | ||||
ret = "" | ||||
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 | ||||
self.data.insert(0, "".join(self.data)) | ||||
del self.data[1:] | ||||
Matt Mackall
|
r10282 | s = self.data[0][doff:doff + count] | ||
Matt Mackall
|
r4261 | self.offset += len(s) | ||
ret += s | ||||
return ret | ||||
def write(self, s): | ||||
Yuya Nishihara
|
r31642 | self.data.append(bytes(s)) | ||
Matt Mackall
|
r4261 | self.offset += len(s) | ||
Durham Goode
|
r30596 | self._end += len(s) | ||
Matt Mackall
|
r4261 | |||
Boris Feld
|
r35980 | def __enter__(self): | ||
self.fp.__enter__() | ||||
return self | ||||
def __exit__(self, *args): | ||||
return self.fp.__exit__(*args) | ||||
Pierre-Yves David
|
r23201 | def _divertopener(opener, target): | ||
"""build an opener that writes in 'target.a' instead of 'target'""" | ||||
FUJIWARA Katsunori
|
r29997 | def _divert(name, mode='r', checkambig=False): | ||
Matt Mackall
|
r9166 | if name != target: | ||
return opener(name, mode) | ||||
Pierre-Yves David
|
r23201 | return opener(name + ".a", mode) | ||
return _divert | ||||
def _delayopener(opener, target, buf): | ||||
"""build an opener that stores chunks in 'buf' instead of 'target'""" | ||||
FUJIWARA Katsunori
|
r29997 | def _delay(name, mode='r', checkambig=False): | ||
Pierre-Yves David
|
r23201 | if name != target: | ||
return opener(name, mode) | ||||
FUJIWARA Katsunori
|
r19899 | return appender(opener, name, mode, buf) | ||
Pierre-Yves David
|
r23201 | return _delay | ||
Matt Mackall
|
r9166 | |||
Siddharth Agarwal
|
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) | ||||
user = attr.ib(default='') | ||||
date = attr.ib(default=(0, 0)) | ||||
Gregory Szorc
|
r34442 | files = attr.ib(default=attr.Factory(list)) | ||
Martin von Zweigbergk
|
r42599 | filesadded = attr.ib(default=None) | ||
filesremoved = attr.ib(default=None) | ||||
Martin von Zweigbergk
|
r42487 | p1copies = attr.ib(default=None) | ||
p2copies = attr.ib(default=None) | ||||
Siddharth Agarwal
|
r34399 | description = attr.ib(default='') | ||
Gregory Szorc
|
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__ = ( | ||||
Gregory Szorc
|
r41997 | r'_offsets', | ||
r'_text', | ||||
Gregory Szorc
|
r28487 | ) | ||
def __new__(cls, text): | ||||
if not text: | ||||
Siddharth Agarwal
|
r34399 | return _changelogrevision(extra=_defaultextra) | ||
Gregory Szorc
|
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 | ||||
Gregory Szorc
|
r28490 | nl1 = text.index('\n') | ||
Gregory Szorc
|
r28491 | nl2 = text.index('\n', nl1 + 1) | ||
Gregory Szorc
|
r28492 | nl3 = text.index('\n', nl2 + 1) | ||
Gregory Szorc
|
r28487 | |||
Gregory Szorc
|
r28493 | # The list of files may be empty. Which means nl3 is the first of the | ||
# double newline that precedes the description. | ||||
Pulkit Goyal
|
r32153 | if text[nl3 + 1:nl3 + 2] == '\n': | ||
Gregory Szorc
|
r28495 | doublenl = nl3 | ||
Gregory Szorc
|
r28493 | else: | ||
Gregory Szorc
|
r28494 | doublenl = text.index('\n\n', nl3 + 1) | ||
Gregory Szorc
|
r28495 | |||
self._offsets = (nl1, nl2, nl3, doublenl) | ||||
self._text = text | ||||
Gregory Szorc
|
r28487 | |||
return self | ||||
Gregory Szorc
|
r28489 | @property | ||
Gregory Szorc
|
r28490 | def manifest(self): | ||
Gregory Szorc
|
r28495 | return bin(self._text[0:self._offsets[0]]) | ||
Gregory Szorc
|
r28490 | |||
@property | ||||
Gregory Szorc
|
r28491 | def user(self): | ||
Gregory Szorc
|
r28495 | off = self._offsets | ||
return encoding.tolocal(self._text[off[0] + 1:off[1]]) | ||||
Gregory Szorc
|
r28491 | |||
@property | ||||
Gregory Szorc
|
r28492 | def _rawdate(self): | ||
Gregory Szorc
|
r28495 | off = self._offsets | ||
dateextra = self._text[off[1] + 1:off[2]] | ||||
return dateextra.split(' ', 2)[0:2] | ||||
Gregory Szorc
|
r28492 | |||
@property | ||||
def _rawextra(self): | ||||
Gregory Szorc
|
r28495 | off = self._offsets | ||
dateextra = self._text[off[1] + 1:off[2]] | ||||
fields = dateextra.split(' ', 2) | ||||
Gregory Szorc
|
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 | ||||
Gregory Szorc
|
r28493 | def files(self): | ||
Gregory Szorc
|
r28495 | off = self._offsets | ||
if off[2] == off[3]: | ||||
Gregory Szorc
|
r28493 | return [] | ||
Gregory Szorc
|
r28495 | return self._text[off[2] + 1:off[3]].split('\n') | ||
Gregory Szorc
|
r28493 | |||
@property | ||||
Martin von Zweigbergk
|
r42599 | def filesadded(self): | ||
rawindices = self.extra.get('filesadded') | ||||
return rawindices and decodefileindices(self.files, rawindices) | ||||
@property | ||||
def filesremoved(self): | ||||
rawindices = self.extra.get('filesremoved') | ||||
return rawindices and decodefileindices(self.files, rawindices) | ||||
@property | ||||
Martin von Zweigbergk
|
r42318 | def p1copies(self): | ||
rawcopies = self.extra.get('p1copies') | ||||
Martin von Zweigbergk
|
r42619 | return rawcopies and decodecopies(self.files, rawcopies) | ||
Martin von Zweigbergk
|
r42318 | |||
@property | ||||
def p2copies(self): | ||||
rawcopies = self.extra.get('p2copies') | ||||
Martin von Zweigbergk
|
r42619 | return rawcopies and decodecopies(self.files, rawcopies) | ||
Martin von Zweigbergk
|
r42318 | |||
@property | ||||
Gregory Szorc
|
r28489 | def description(self): | ||
Gregory Szorc
|
r28495 | return encoding.tolocal(self._text[self._offsets[3] + 2:]) | ||
Gregory Szorc
|
r28489 | |||
Matt Mackall
|
r7634 | class changelog(revlog.revlog): | ||
Gregory Szorc
|
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. | ||||
""" | ||||
if trypending and opener.exists('00changelog.i.a'): | ||||
indexfile = '00changelog.i.a' | ||||
else: | ||||
indexfile = '00changelog.i' | ||||
Jun Wu
|
r32307 | datafile = '00changelog.d' | ||
revlog.revlog.__init__(self, opener, indexfile, datafile=datafile, | ||||
Mark Thomas
|
r34297 | checkambig=True, mmaplargeindex=True) | ||
Gregory Szorc
|
r32292 | |||
Gregory Szorc
|
r41238 | if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1): | ||
# changelogs don't benefit from generaldelta. | ||||
Gregory Szorc
|
r32316 | self.version &= ~revlog.FLAG_GENERALDELTA | ||
Sune Foldager
|
r14334 | self._generaldelta = False | ||
Gregory Szorc
|
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. | ||||
Gregory Szorc
|
r39268 | self._storedeltachains = False | ||
Gregory Szorc
|
r30155 | |||
Matt Mackall
|
r8644 | self._realopener = opener | ||
self._delayed = False | ||||
Pierre-Yves David
|
r23201 | self._delaybuf = None | ||
Matt Mackall
|
r9163 | self._divert = False | ||
Pierre-Yves David
|
r18231 | self.filteredrevs = frozenset() | ||
Pierre-Yves David
|
r17677 | |||
Boris Feld
|
r35689 | def tiprev(self): | ||
Gregory Szorc
|
r38806 | for i in pycompat.xrange(len(self) -1, -2, -1): | ||
Boris Feld
|
r35689 | if i not in self.filteredrevs: | ||
return i | ||||
Pierre-Yves David
|
r17677 | def tip(self): | ||
"""filtered version of revlog.tip""" | ||||
Boris Feld
|
r35690 | return self.node(self.tiprev()) | ||
Pierre-Yves David
|
r17677 | |||
Yuya Nishihara
|
r24030 | def __contains__(self, rev): | ||
"""filtered version of revlog.__contains__""" | ||||
Yuya Nishihara
|
r24662 | return (0 <= rev < len(self) | ||
Yuya Nishihara
|
r24030 | and rev not in self.filteredrevs) | ||
Pierre-Yves David
|
r17677 | def __iter__(self): | ||
"""filtered version of revlog.__iter__""" | ||||
Durham Goode
|
r17951 | if len(self.filteredrevs) == 0: | ||
return revlog.revlog.__iter__(self) | ||||
def filterediter(): | ||||
Gregory Szorc
|
r38806 | for i in pycompat.xrange(len(self)): | ||
Durham Goode
|
r17951 | if i not in self.filteredrevs: | ||
yield i | ||||
return filterediter() | ||||
Pierre-Yves David
|
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 | ||||
Georges Racinet
|
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 | ||||
Boris Feld
|
r41311 | def headrevs(self, revs=None): | ||
if revs is None and self.filteredrevs: | ||||
Durham Goode
|
r22484 | try: | ||
Mads Kiilerich
|
r23088 | return self.index.headrevsfiltered(self.filteredrevs) | ||
# AttributeError covers non-c-extension environments and | ||||
# old c extensions without filter handling. | ||||
except AttributeError: | ||||
Durham Goode
|
r22484 | return self._headrevs() | ||
Georges Racinet
|
r41929 | if self.filteredrevs: | ||
revs = self._checknofilteredinrevs(revs) | ||||
Boris Feld
|
r41311 | return super(changelog, self).headrevs(revs) | ||
Pierre-Yves David
|
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: | ||||
Pierre-Yves David
|
r23015 | raise error.FilteredLookupError(hex(node), self.indexfile, | ||
_('filtered node')) | ||||
Pierre-Yves David
|
r17677 | return r | ||
def node(self, rev): | ||||
"""filtered version of revlog.node""" | ||||
if rev in self.filteredrevs: | ||||
Pierre-Yves David
|
r23014 | raise error.FilteredIndexError(rev) | ||
Pierre-Yves David
|
r17677 | return super(changelog, self).node(rev) | ||
def linkrev(self, rev): | ||||
"""filtered version of revlog.linkrev""" | ||||
if rev in self.filteredrevs: | ||||
Pierre-Yves David
|
r23014 | raise error.FilteredIndexError(rev) | ||
Pierre-Yves David
|
r17677 | return super(changelog, self).linkrev(rev) | ||
def parentrevs(self, rev): | ||||
"""filtered version of revlog.parentrevs""" | ||||
if rev in self.filteredrevs: | ||||
Pierre-Yves David
|
r23014 | raise error.FilteredIndexError(rev) | ||
Pierre-Yves David
|
r17677 | return super(changelog, self).parentrevs(rev) | ||
def flags(self, rev): | ||||
"""filtered version of revlog.flags""" | ||||
if rev in self.filteredrevs: | ||||
Pierre-Yves David
|
r23014 | raise error.FilteredIndexError(rev) | ||
Pierre-Yves David
|
r17677 | return super(changelog, self).flags(rev) | ||
mpm@selenic.com
|
r1089 | |||
Pierre-Yves David
|
r23203 | def delayupdate(self, tr): | ||
Matt Mackall
|
r4261 | "delay visibility of index updates to other readers" | ||
Pierre-Yves David
|
r23201 | |||
if not self._delayed: | ||||
if len(self) == 0: | ||||
self._divert = True | ||||
if self._realopener.exists(self.indexfile + '.a'): | ||||
self._realopener.unlink(self.indexfile + '.a') | ||||
self.opener = _divertopener(self._realopener, self.indexfile) | ||||
else: | ||||
self._delaybuf = [] | ||||
self.opener = _delayopener(self._realopener, self.indexfile, | ||||
self._delaybuf) | ||||
Matt Mackall
|
r8644 | self._delayed = True | ||
Pierre-Yves David
|
r23203 | tr.addpending('cl-%i' % id(self), self._writepending) | ||
Pierre-Yves David
|
r23281 | tr.addfinalize('cl-%i' % id(self), self._finalize) | ||
Matt Mackall
|
r4261 | |||
Pierre-Yves David
|
r23205 | def _finalize(self, tr): | ||
Matt Mackall
|
r4261 | "finalize index updates" | ||
Matt Mackall
|
r8644 | self._delayed = False | ||
Matt Mackall
|
r9165 | self.opener = self._realopener | ||
Matt Mackall
|
r4269 | # move redirected index data back into place | ||
Matt Mackall
|
r9164 | if self._divert: | ||
Pierre-Yves David
|
r23201 | assert not self._delaybuf | ||
FUJIWARA Katsunori
|
r19898 | tmpname = self.indexfile + ".a" | ||
nfile = self.opener.open(tmpname) | ||||
Zachary Gramana
|
r14207 | nfile.close() | ||
FUJIWARA Katsunori
|
r29999 | self.opener.rename(tmpname, self.indexfile, checkambig=True) | ||
Matt Mackall
|
r4269 | elif self._delaybuf: | ||
FUJIWARA Katsunori
|
r29999 | fp = self.opener(self.indexfile, 'a', checkambig=True) | ||
Matt Mackall
|
r4261 | fp.write("".join(self._delaybuf)) | ||
fp.close() | ||||
Pierre-Yves David
|
r23201 | self._delaybuf = None | ||
self._divert = False | ||||
Matt Mackall
|
r4269 | # split when we're done | ||
Boris Feld
|
r35992 | self._enforceinlinesize(tr) | ||
Matt Mackall
|
r4261 | |||
Pierre-Yves David
|
r23280 | def _writepending(self, tr): | ||
Matt Mackall
|
r7787 | "create a file containing the unfinalized state for pretxnchangegroup" | ||
if self._delaybuf: | ||||
# make a temporary copy of the index | ||||
fp1 = self._realopener(self.indexfile) | ||||
Pierre-Yves David
|
r23292 | pendingfilename = self.indexfile + ".a" | ||
# register as a temp file to ensure cleanup on failure | ||||
tr.registertmp(pendingfilename) | ||||
# write existing data | ||||
fp2 = self._realopener(pendingfilename, "w") | ||||
Matt Mackall
|
r7787 | fp2.write(fp1.read()) | ||
# add pending data | ||||
fp2.write("".join(self._delaybuf)) | ||||
fp2.close() | ||||
# switch modes so finalize can simply rename | ||||
Pierre-Yves David
|
r23201 | self._delaybuf = None | ||
Matt Mackall
|
r9164 | self._divert = True | ||
Pierre-Yves David
|
r23201 | self.opener = _divertopener(self._realopener, self.indexfile) | ||
Matt Mackall
|
r7787 | |||
Matt Mackall
|
r9164 | if self._divert: | ||
Matt Mackall
|
r7787 | return True | ||
return False | ||||
Boris Feld
|
r35992 | def _enforceinlinesize(self, tr, fp=None): | ||
Matt Mackall
|
r9165 | if not self._delayed: | ||
Boris Feld
|
r35992 | revlog.revlog._enforceinlinesize(self, tr, fp) | ||
Matt Mackall
|
r4261 | |||
Matt Mackall
|
r5744 | def read(self, node): | ||
Gregory Szorc
|
r28487 | """Obtain data from a parsed changelog revision. | ||
Returns a 6-tuple of: | ||||
Benoit Boissinot
|
r3233 | |||
Gregory Szorc
|
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. | ||||
Benoit Boissinot
|
r3077 | """ | ||
Gregory Szorc
|
r28487 | c = changelogrevision(self.revision(node)) | ||
return ( | ||||
c.manifest, | ||||
c.user, | ||||
c.date, | ||||
c.files, | ||||
c.description, | ||||
c.extra | ||||
) | ||||
Benoit Boissinot
|
r3233 | |||
Gregory Szorc
|
r28487 | def changelogrevision(self, nodeorrev): | ||
"""Obtain a ``changelogrevision`` for a node or revision.""" | ||||
return changelogrevision(self.revision(nodeorrev)) | ||||
mpm@selenic.com
|
r1089 | |||
Laurent Charignon
|
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 [] | ||||
last = text.index("\n\n") | ||||
l = text[:last].split('\n') | ||||
return l[3:] | ||||
Martin Geisler
|
r8422 | def add(self, manifest, files, desc, transaction, p1, p2, | ||
Martin von Zweigbergk
|
r42598 | user, date=None, extra=None, p1copies=None, p2copies=None, | ||
filesadded=None, filesremoved=None): | ||||
Martin Geisler
|
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) | ||||
Benoit Boissinot
|
r7035 | user = user.strip() | ||
Martin Geisler
|
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: | ||||
Gregory Szorc
|
r39813 | raise error.StorageError(_("empty username")) | ||
Benoit Boissinot
|
r7035 | if "\n" in user: | ||
Gregory Szorc
|
r39813 | raise error.StorageError(_("username %r contains a newline") | ||
% pycompat.bytestr(user)) | ||||
Matt Mackall
|
r8499 | |||
Pierre-Yves David
|
r17810 | desc = stripdesc(desc) | ||
Matt Mackall
|
r8499 | |||
Bryan O'Sullivan
|
r1195 | if date: | ||
Boris Feld
|
r36625 | parseddate = "%d %d" % dateutil.parsedate(date) | ||
Bryan O'Sullivan
|
r1195 | else: | ||
Boris Feld
|
r36625 | parseddate = "%d %d" % dateutil.makedate() | ||
Wagner Bruna
|
r10417 | if extra: | ||
branch = extra.get("branch") | ||||
if branch in ("default", ""): | ||||
del extra["branch"] | ||||
elif branch in (".", "null", "tip"): | ||||
Gregory Szorc
|
r39813 | raise error.StorageError(_('the name \'%s\' is reserved') | ||
% branch) | ||||
Martin von Zweigbergk
|
r42598 | extrasentries = p1copies, p2copies, filesadded, filesremoved | ||
if extra is None and any(x is not None for x in extrasentries): | ||||
Martin von Zweigbergk
|
r42317 | extra = {} | ||
Martin von Zweigbergk
|
r42619 | sortedfiles = sorted(files) | ||
Martin von Zweigbergk
|
r43127 | if extra is not None: | ||
for name in ('p1copies', 'p2copies', 'filesadded', 'filesremoved'): | ||||
extra.pop(name, None) | ||||
Martin von Zweigbergk
|
r42486 | if p1copies is not None: | ||
Martin von Zweigbergk
|
r42619 | extra['p1copies'] = encodecopies(sortedfiles, p1copies) | ||
Martin von Zweigbergk
|
r42486 | if p2copies is not None: | ||
Martin von Zweigbergk
|
r42619 | extra['p2copies'] = encodecopies(sortedfiles, p2copies) | ||
Martin von Zweigbergk
|
r42598 | if filesadded is not None: | ||
extra['filesadded'] = encodefileindices(sortedfiles, filesadded) | ||||
if filesremoved is not None: | ||||
extra['filesremoved'] = encodefileindices(sortedfiles, filesremoved) | ||||
Martin von Zweigbergk
|
r42317 | |||
Benoit Boissinot
|
r3233 | if extra: | ||
Martin Geisler
|
r8443 | extra = encodeextra(extra) | ||
Benoit Boissinot
|
r3233 | parseddate = "%s %s" % (parseddate, extra) | ||
Martin von Zweigbergk
|
r42598 | l = [hex(manifest), user, parseddate] + sortedfiles + ["", desc] | ||
mpm@selenic.com
|
r1089 | text = "\n".join(l) | ||
Matt Mackall
|
r6750 | return self.addrevision(text, transaction, len(self), p1, p2) | ||
Pierre-Yves David
|
r18306 | |||
Brodie Rao
|
r20185 | def branchinfo(self, rev): | ||
"""return the branch name and open/close state of a revision | ||||
Pierre-Yves David
|
r18306 | |||
Mads Kiilerich
|
r18308 | This function exists because creating a changectx object | ||
just to access this is costly.""" | ||||
Brodie Rao
|
r20185 | extra = self.read(rev)[5] | ||
return encoding.tolocal(extra.get("branch")), 'close' in extra | ||||
Boris Feld
|
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 | ||||
duplicates = transaction.changes.setdefault('revduplicates', []) | ||||
duplicates.append(self.rev(node)) | ||||