changelog.py
618 lines
| 18.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, | ||||
) | ||||
Augie Fackler
|
r43346 | from .thirdparty import attr | ||
Gregory Szorc
|
r25922 | |||
from . import ( | ||||
encoding, | ||||
error, | ||||
r45466 | metadata, | |||
Pulkit Goyal
|
r36246 | pycompat, | ||
Gregory Szorc
|
r25922 | revlog, | ||
) | ||||
Yuya Nishihara
|
r37102 | from .utils import ( | ||
dateutil, | ||||
stringutil, | ||||
) | ||||
r46263 | from .revlogutils import flagutil | |||
mpm@selenic.com
|
r1089 | |||
Augie Fackler
|
r43347 | _defaultextra = {b'branch': b'default'} | ||
Matt Mackall
|
r16267 | |||
Augie Fackler
|
r43346 | |||
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 | ||||
Augie Fackler
|
r43347 | text = ( | ||
text.replace(b'\\', b'\\\\') | ||||
.replace(b'\n', b'\\n') | ||||
.replace(b'\r', b'\\r') | ||||
) | ||||
return text.replace(b'\0', b'\\0') | ||||
Benoit Boissinot
|
r3232 | |||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r42285 | def _string_unescape(text): | ||
Augie Fackler
|
r43347 | if b'\\0' in text: | ||
Martin von Zweigbergk
|
r42285 | # fix up \0 without getting into trouble with \\0 | ||
Augie Fackler
|
r43347 | text = text.replace(b'\\\\', b'\\\\\n') | ||
text = text.replace(b'\\0', b'\0') | ||||
text = text.replace(b'\n', b'') | ||||
Martin von Zweigbergk
|
r42285 | return stringutil.unescapestr(text) | ||
Augie Fackler
|
r43346 | |||
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() | ||
Augie Fackler
|
r43347 | for l in text.split(b'\0'): | ||
Martin Geisler
|
r8443 | if l: | ||
Augie Fackler
|
r43347 | k, v = _string_unescape(l).split(b':', 1) | ||
Martin Geisler
|
r8443 | extra[k] = v | ||
return extra | ||||
Augie Fackler
|
r43346 | |||
Martin Geisler
|
r8443 | def encodeextra(d): | ||
# keys must be sorted to produce a deterministic changelog entry | ||||
Martin von Zweigbergk
|
r45095 | items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)] | ||
Augie Fackler
|
r43347 | return b"\0".join(items) | ||
Martin Geisler
|
r8443 | |||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r17810 | def stripdesc(desc): | ||
"""strip trailing whitespace and leading and trailing empty lines""" | ||||
Augie Fackler
|
r43347 | return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n') | ||
Pierre-Yves David
|
r17810 | |||
Augie Fackler
|
r43346 | |||
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''' | ||
Augie Fackler
|
r43346 | |||
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 | ||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r4261 | def tell(self): | ||
return self.offset | ||||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r4261 | 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''' | ||||
Augie Fackler
|
r43347 | ret = b"" | ||
Matt Mackall
|
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 | ||||
Augie Fackler
|
r43347 | self.data.insert(0, b"".join(self.data)) | ||
Matt Mackall
|
r4261 | del self.data[1:] | ||
Augie Fackler
|
r43346 | 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) | ||||
Augie Fackler
|
r43346 | |||
r44985 | class _divertopener(object): | |||
def __init__(self, opener, target): | ||||
self._opener = opener | ||||
self._target = target | ||||
Augie Fackler
|
r43346 | |||
r44985 | def __call__(self, name, mode=b'r', checkambig=False, **kwargs): | |||
if name != self._target: | ||||
return self._opener(name, mode, **kwargs) | ||||
return self._opener(name + b".a", mode, **kwargs) | ||||
Augie Fackler
|
r43346 | |||
r44985 | def __getattr__(self, attr): | |||
return getattr(self._opener, attr) | ||||
Pierre-Yves David
|
r23201 | |||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r23201 | def _delayopener(opener, target, buf): | ||
"""build an opener that stores chunks in 'buf' instead of 'target'""" | ||||
Augie Fackler
|
r43346 | |||
r44507 | def _delay(name, mode=b'r', checkambig=False, **kwargs): | |||
Pierre-Yves David
|
r23201 | if name != target: | ||
r44507 | return opener(name, mode, **kwargs) | |||
assert not kwargs | ||||
FUJIWARA Katsunori
|
r19899 | return appender(opener, name, mode, buf) | ||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r23201 | return _delay | ||
Matt Mackall
|
r9166 | |||
Augie Fackler
|
r43346 | |||
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) | ||||
Augie Fackler
|
r43347 | user = attr.ib(default=b'') | ||
Siddharth Agarwal
|
r34399 | 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) | ||||
Augie Fackler
|
r43347 | description = attr.ib(default=b'') | ||
Gregory Szorc
|
r28487 | |||
Augie Fackler
|
r43346 | |||
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__ = ( | ||||
Augie Fackler
|
r43906 | '_offsets', | ||
'_text', | ||||
'_sidedata', | ||||
'_cpsd', | ||||
r46144 | '_changes', | |||
Gregory Szorc
|
r28487 | ) | ||
r43504 | def __new__(cls, text, sidedata, cpsd): | |||
Gregory Szorc
|
r28487 | 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 | ||||
Augie Fackler
|
r43347 | nl1 = text.index(b'\n') | ||
nl2 = text.index(b'\n', nl1 + 1) | ||||
nl3 = text.index(b'\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. | ||||
Augie Fackler
|
r43347 | if text[nl3 + 1 : nl3 + 2] == b'\n': | ||
Gregory Szorc
|
r28495 | doublenl = nl3 | ||
Gregory Szorc
|
r28493 | else: | ||
Augie Fackler
|
r43347 | doublenl = text.index(b'\n\n', nl3 + 1) | ||
Gregory Szorc
|
r28495 | |||
self._offsets = (nl1, nl2, nl3, doublenl) | ||||
self._text = text | ||||
r43413 | self._sidedata = sidedata | |||
r43504 | self._cpsd = cpsd | |||
r46144 | self._changes = None | |||
Gregory Szorc
|
r28487 | |||
return self | ||||
Gregory Szorc
|
r28489 | @property | ||
Gregory Szorc
|
r28490 | def manifest(self): | ||
Augie Fackler
|
r43346 | return bin(self._text[0 : self._offsets[0]]) | ||
Gregory Szorc
|
r28490 | |||
@property | ||||
Gregory Szorc
|
r28491 | def user(self): | ||
Gregory Szorc
|
r28495 | off = self._offsets | ||
Augie Fackler
|
r43346 | 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 | ||
Augie Fackler
|
r43346 | dateextra = self._text[off[1] + 1 : off[2]] | ||
Augie Fackler
|
r43347 | return dateextra.split(b' ', 2)[0:2] | ||
Gregory Szorc
|
r28492 | |||
@property | ||||
def _rawextra(self): | ||||
Gregory Szorc
|
r28495 | off = self._offsets | ||
Augie Fackler
|
r43346 | dateextra = self._text[off[1] + 1 : off[2]] | ||
Augie Fackler
|
r43347 | fields = dateextra.split(b' ', 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 | ||||
r46144 | def changes(self): | |||
if self._changes is not None: | ||||
return self._changes | ||||
r46145 | if self._cpsd: | |||
r46212 | changes = metadata.decode_files_sidedata(self._sidedata) | |||
r46145 | else: | |||
changes = metadata.ChangingFiles( | ||||
touched=self.files or (), | ||||
added=self.filesadded or (), | ||||
removed=self.filesremoved or (), | ||||
p1_copies=self.p1copies or {}, | ||||
p2_copies=self.p2copies or {}, | ||||
) | ||||
r46144 | self._changes = changes | |||
return changes | ||||
@property | ||||
Gregory Szorc
|
r28493 | def files(self): | ||
r46213 | if self._cpsd: | |||
return sorted(self.changes.touched) | ||||
Gregory Szorc
|
r28495 | off = self._offsets | ||
if off[2] == off[3]: | ||||
Gregory Szorc
|
r28493 | return [] | ||
Augie Fackler
|
r43347 | return self._text[off[2] + 1 : off[3]].split(b'\n') | ||
Gregory Szorc
|
r28493 | |||
@property | ||||
Martin von Zweigbergk
|
r42599 | def filesadded(self): | ||
r43504 | if self._cpsd: | |||
r46146 | return self.changes.added | |||
r43416 | else: | |||
rawindices = self.extra.get(b'filesadded') | ||||
r43415 | if rawindices is None: | |||
return None | ||||
r45466 | return metadata.decodefileindices(self.files, rawindices) | |||
Martin von Zweigbergk
|
r42599 | |||
@property | ||||
def filesremoved(self): | ||||
r43504 | if self._cpsd: | |||
r46147 | return self.changes.removed | |||
r43416 | else: | |||
rawindices = self.extra.get(b'filesremoved') | ||||
r43415 | if rawindices is None: | |||
return None | ||||
r45466 | return metadata.decodefileindices(self.files, rawindices) | |||
Martin von Zweigbergk
|
r42599 | |||
@property | ||||
Martin von Zweigbergk
|
r42318 | def p1copies(self): | ||
r43504 | if self._cpsd: | |||
r46148 | return self.changes.copied_from_p1 | |||
r43416 | else: | |||
rawcopies = self.extra.get(b'p1copies') | ||||
r43415 | if rawcopies is None: | |||
return None | ||||
r45466 | return metadata.decodecopies(self.files, rawcopies) | |||
Martin von Zweigbergk
|
r42318 | |||
@property | ||||
def p2copies(self): | ||||
r43504 | if self._cpsd: | |||
r46149 | return self.changes.copied_from_p2 | |||
r43416 | else: | |||
rawcopies = self.extra.get(b'p2copies') | ||||
r43415 | if rawcopies is None: | |||
return None | ||||
r45466 | return metadata.decodecopies(self.files, rawcopies) | |||
Martin von Zweigbergk
|
r42318 | |||
@property | ||||
Gregory Szorc
|
r28489 | def description(self): | ||
Augie Fackler
|
r43346 | 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. | ||||
""" | ||||
Augie Fackler
|
r43347 | if trypending and opener.exists(b'00changelog.i.a'): | ||
indexfile = b'00changelog.i.a' | ||||
Gregory Szorc
|
r32292 | else: | ||
Augie Fackler
|
r43347 | indexfile = b'00changelog.i' | ||
Gregory Szorc
|
r32292 | |||
Augie Fackler
|
r43347 | datafile = b'00changelog.d' | ||
Augie Fackler
|
r43346 | revlog.revlog.__init__( | ||
self, | ||||
opener, | ||||
indexfile, | ||||
datafile=datafile, | ||||
checkambig=True, | ||||
mmaplargeindex=True, | ||||
r45296 | persistentnodemap=opener.options.get(b'persistent-nodemap', False), | |||
Augie Fackler
|
r43346 | ) | ||
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 | ||
Kyle Lippincott
|
r46088 | self._filteredrevs = frozenset() | ||
self._filteredrevs_hashcache = {} | ||||
Augie Fackler
|
r43347 | self._copiesstorage = opener.options.get(b'copies-storage') | ||
Pierre-Yves David
|
r17677 | |||
Kyle Lippincott
|
r46088 | @property | ||
def filteredrevs(self): | ||||
return self._filteredrevs | ||||
@filteredrevs.setter | ||||
def filteredrevs(self, val): | ||||
# Ensure all updates go through this function | ||||
assert isinstance(val, frozenset) | ||||
self._filteredrevs = val | ||||
self._filteredrevs_hashcache = {} | ||||
Pierre-Yves David
|
r23203 | def delayupdate(self, tr): | ||
Matt Harbison
|
r44226 | """delay visibility of index updates to other readers""" | ||
Pierre-Yves David
|
r23201 | |||
if not self._delayed: | ||||
if len(self) == 0: | ||||
self._divert = True | ||||
Augie Fackler
|
r43347 | if self._realopener.exists(self.indexfile + b'.a'): | ||
self._realopener.unlink(self.indexfile + b'.a') | ||||
Pierre-Yves David
|
r23201 | self.opener = _divertopener(self._realopener, self.indexfile) | ||
else: | ||||
self._delaybuf = [] | ||||
Augie Fackler
|
r43346 | self.opener = _delayopener( | ||
self._realopener, self.indexfile, self._delaybuf | ||||
) | ||||
Matt Mackall
|
r8644 | self._delayed = True | ||
Augie Fackler
|
r43347 | tr.addpending(b'cl-%i' % id(self), self._writepending) | ||
tr.addfinalize(b'cl-%i' % id(self), self._finalize) | ||||
Matt Mackall
|
r4261 | |||
Pierre-Yves David
|
r23205 | def _finalize(self, tr): | ||
Matt Harbison
|
r44226 | """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 | ||
Augie Fackler
|
r43347 | tmpname = self.indexfile + b".a" | ||
FUJIWARA Katsunori
|
r19898 | 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: | ||
Augie Fackler
|
r43347 | fp = self.opener(self.indexfile, b'a', checkambig=True) | ||
fp.write(b"".join(self._delaybuf)) | ||||
Matt Mackall
|
r4261 | 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 Harbison
|
r44226 | """create a file containing the unfinalized state for | ||
pretxnchangegroup""" | ||||
Matt Mackall
|
r7787 | if self._delaybuf: | ||
# make a temporary copy of the index | ||||
fp1 = self._realopener(self.indexfile) | ||||
Augie Fackler
|
r43347 | pendingfilename = self.indexfile + b".a" | ||
Pierre-Yves David
|
r23292 | # register as a temp file to ensure cleanup on failure | ||
tr.registertmp(pendingfilename) | ||||
# write existing data | ||||
Augie Fackler
|
r43347 | fp2 = self._realopener(pendingfilename, b"w") | ||
Matt Mackall
|
r7787 | fp2.write(fp1.read()) | ||
# add pending data | ||||
Augie Fackler
|
r43347 | fp2.write(b"".join(self._delaybuf)) | ||
Matt Mackall
|
r7787 | 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 | """ | ||
r43504 | d, s = self._revisiondata(node) | |||
c = changelogrevision( | ||||
d, s, self._copiesstorage == b'changeset-sidedata' | ||||
) | ||||
Augie Fackler
|
r43346 | 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.""" | ||||
r43413 | text, sidedata = self._revisiondata(nodeorrev) | |||
r43504 | return changelogrevision( | |||
text, sidedata, self._copiesstorage == b'changeset-sidedata' | ||||
) | ||||
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 [] | ||||
Augie Fackler
|
r43347 | last = text.index(b"\n\n") | ||
l = text[:last].split(b'\n') | ||||
Laurent Charignon
|
r27439 | return l[3:] | ||
Augie Fackler
|
r43346 | def add( | ||
self, | ||||
manifest, | ||||
files, | ||||
desc, | ||||
transaction, | ||||
p1, | ||||
p2, | ||||
user, | ||||
date=None, | ||||
extra=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: | ||||
Augie Fackler
|
r43347 | raise error.StorageError(_(b"empty username")) | ||
if b"\n" in user: | ||||
Augie Fackler
|
r43346 | raise error.StorageError( | ||
Augie Fackler
|
r43347 | _(b"username %r contains a newline") % pycompat.bytestr(user) | ||
Augie Fackler
|
r43346 | ) | ||
Matt Mackall
|
r8499 | |||
Pierre-Yves David
|
r17810 | desc = stripdesc(desc) | ||
Matt Mackall
|
r8499 | |||
Bryan O'Sullivan
|
r1195 | if date: | ||
Augie Fackler
|
r43347 | parseddate = b"%d %d" % dateutil.parsedate(date) | ||
Bryan O'Sullivan
|
r1195 | else: | ||
Augie Fackler
|
r43347 | parseddate = b"%d %d" % dateutil.makedate() | ||
Wagner Bruna
|
r10417 | if extra: | ||
Augie Fackler
|
r43347 | branch = extra.get(b"branch") | ||
if branch in (b"default", b""): | ||||
del extra[b"branch"] | ||||
elif branch in (b".", b"null", b"tip"): | ||||
Augie Fackler
|
r43346 | raise error.StorageError( | ||
Augie Fackler
|
r43347 | _(b'the name \'%s\' is reserved') % branch | ||
Augie Fackler
|
r43346 | ) | ||
r45884 | sortedfiles = sorted(files.touched) | |||
r46263 | flags = 0 | |||
r43412 | sidedata = None | |||
r45809 | if self._copiesstorage == b'changeset-sidedata': | |||
r46263 | if ( | |||
files.removed | ||||
or files.merged | ||||
or files.salvaged | ||||
or files.copied_from_p1 | ||||
or files.copied_from_p2 | ||||
): | ||||
flags |= flagutil.REVIDX_HASCOPIESINFO | ||||
r46143 | sidedata = metadata.encode_files_sidedata(files) | |||
Martin von Zweigbergk
|
r42317 | |||
Benoit Boissinot
|
r3233 | if extra: | ||
Martin Geisler
|
r8443 | extra = encodeextra(extra) | ||
Augie Fackler
|
r43347 | parseddate = b"%s %s" % (parseddate, extra) | ||
l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc] | ||||
text = b"\n".join(l) | ||||
r43412 | return self.addrevision( | |||
r46264 | text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags | |||
r43412 | ) | |||
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] | ||
Augie Fackler
|
r43347 | return encoding.tolocal(extra.get(b"branch")), b'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 | ||||
Augie Fackler
|
r43347 | duplicates = transaction.changes.setdefault(b'revduplicates', []) | ||
Boris Feld
|
r39923 | duplicates.append(self.rev(node)) | ||