changelog.py
520 lines
| 16.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / changelog.py
mpm@selenic.com
|
r1095 | # changelog.py - changelog class for mercurial | ||
mpm@selenic.com
|
r1089 | # | ||
Raphaël Gomès
|
r47575 | # Copyright 2005-2007 Olivia Mackall <olivia@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 | |||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Gregory Szorc
|
r25922 | |||
Matt Harbison
|
r52622 | import typing | ||
Gregory Szorc
|
r25922 | from .i18n import _ | ||
from .node import ( | ||||
bin, | ||||
hex, | ||||
) | ||||
Augie Fackler
|
r43346 | from .thirdparty import attr | ||
Gregory Szorc
|
r25922 | |||
Matt Harbison
|
r52622 | # Force pytype to use the non-vendored package | ||
if typing.TYPE_CHECKING: | ||||
# noinspection PyPackageRequirements | ||||
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, | ||||
) | ||||
r47838 | from .revlogutils import ( | |||
constants as revlog_constants, | ||||
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 | |||
Siddharth Agarwal
|
r34399 | @attr.s | ||
Gregory Szorc
|
r49801 | class _changelogrevision: | ||
Siddharth Agarwal
|
r34399 | # Extensions might modify _defaultextra, so let the constructor below pass | ||
# it in | ||||
extra = attr.ib() | ||||
Joerg Sonnenberger
|
r47538 | manifest = attr.ib() | ||
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'') | ||
Joerg Sonnenberger
|
r47082 | branchinfo = attr.ib(default=(_defaultextra[b'branch'], False)) | ||
Gregory Szorc
|
r28487 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class changelogrevision: | ||
Gregory Szorc
|
r28487 | """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 | ) | ||
Joerg Sonnenberger
|
r47538 | def __new__(cls, cl, text, sidedata, cpsd): | ||
Gregory Szorc
|
r28487 | if not text: | ||
Joerg Sonnenberger
|
r47771 | return _changelogrevision(extra=_defaultextra, manifest=cl.nullid) | ||
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 :]) | ||
Joerg Sonnenberger
|
r47082 | @property | ||
def branchinfo(self): | ||||
extra = self.extra | ||||
return encoding.tolocal(extra.get(b"branch")), b'close' in extra | ||||
Gregory Szorc
|
r28489 | |||
Matt Mackall
|
r7634 | class changelog(revlog.revlog): | ||
Kyle Lippincott
|
r47349 | def __init__(self, opener, trypending=False, concurrencychecker=None): | ||
Gregory Szorc
|
r32292 | """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. | ||||
Kyle Lippincott
|
r47349 | |||
``concurrencychecker`` will be passed to the revlog init function, see | ||||
the documentation there. | ||||
Gregory Szorc
|
r32292 | """ | ||
Augie Fackler
|
r43346 | revlog.revlog.__init__( | ||
self, | ||||
opener, | ||||
r47838 | target=(revlog_constants.KIND_CHANGELOG, None), | |||
r47921 | radix=b'00changelog', | |||
Augie Fackler
|
r43346 | checkambig=True, | ||
mmaplargeindex=True, | ||||
r45296 | persistentnodemap=opener.options.get(b'persistent-nodemap', False), | |||
Kyle Lippincott
|
r47349 | concurrencychecker=concurrencychecker, | ||
r48014 | trypending=trypending, | |||
r52074 | may_inline=False, | |||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r32292 | |||
r47910 | if self._initempty and (self._format_version == revlog.REVLOGV1): | |||
Gregory Szorc
|
r41238 | # changelogs don't benefit from generaldelta. | ||
r47910 | self._format_flags &= ~revlog.FLAG_GENERALDELTA | |||
r51922 | self.delta_config.general_delta = 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 | |||
r51999 | self._v2_delayed = 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 | |||
r52351 | def __contains__(self, rev): | |||
return (0 <= rev < len(self)) and rev not in self._filteredrevs | ||||
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 = {} | ||||
r48013 | def _write_docket(self, tr): | |||
r51999 | if not self._v2_delayed: | |||
r48013 | super(changelog, self)._write_docket(tr) | |||
Pierre-Yves David
|
r23203 | def delayupdate(self, tr): | ||
Matt Harbison
|
r44226 | """delay visibility of index updates to other readers""" | ||
r51995 | assert not self._inner.is_open | |||
r52074 | assert not self._may_inline | |||
# enforce that older changelog that are still inline are split at the | ||||
# first opportunity. | ||||
if self._inline: | ||||
self._enforceinlinesize(tr) | ||||
r51999 | if self._docket is not None: | |||
self._v2_delayed = True | ||||
else: | ||||
new_index = self._inner.delay() | ||||
if new_index is not None: | ||||
self._indexfile = new_index | ||||
tr.registertmp(new_index) | ||||
r52530 | # use "000" as prefix to make sure we run before the spliting of legacy | |||
# inline changelog.. | ||||
tr.addpending(b'000-cl-%i' % id(self), self._writepending) | ||||
tr.addfinalize(b'000-cl-%i' % id(self), self._finalize) | ||||
Matt Mackall
|
r4261 | |||
Pierre-Yves David
|
r23205 | def _finalize(self, tr): | ||
Matt Harbison
|
r44226 | """finalize index updates""" | ||
r51995 | assert not self._inner.is_open | |||
r48013 | if self._docket is not None: | |||
r51999 | self._docket.write(tr) | |||
self._v2_delayed = False | ||||
else: | ||||
new_index_file = self._inner.finalize_pending() | ||||
self._indexfile = new_index_file | ||||
r52209 | if self._inline: | |||
msg = 'changelog should not be inline at that point' | ||||
raise error.ProgrammingError(msg) | ||||
Matt Mackall
|
r4261 | |||
Pierre-Yves David
|
r23280 | def _writepending(self, tr): | ||
Matt Harbison
|
r44226 | """create a file containing the unfinalized state for | ||
pretxnchangegroup""" | ||||
r51995 | assert not self._inner.is_open | |||
r48015 | if self._docket: | |||
r51999 | any_pending = self._docket.write(tr, pending=True) | |||
self._v2_delayed = False | ||||
else: | ||||
new_index, any_pending = self._inner.write_pending() | ||||
if new_index is not None: | ||||
self._indexfile = new_index | ||||
tr.registertmp(new_index) | ||||
return any_pending | ||||
Matt Mackall
|
r7787 | |||
r52210 | def _enforceinlinesize(self, tr): | |||
r51997 | if not self.is_delaying: | |||
r52210 | revlog.revlog._enforceinlinesize(self, tr) | |||
Matt Mackall
|
r4261 | |||
Joerg Sonnenberger
|
r47376 | def read(self, nodeorrev): | ||
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 | """ | ||
r48177 | d = self._revisiondata(nodeorrev) | |||
r48175 | sidedata = self.sidedata(nodeorrev) | |||
copy_sd = self._copiesstorage == b'changeset-sidedata' | ||||
c = changelogrevision(self, d, sidedata, copy_sd) | ||||
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.""" | ||||
r48177 | text = self._revisiondata(nodeorrev) | |||
r48175 | sidedata = self.sidedata(nodeorrev) | |||
r43504 | return changelogrevision( | |||
Joerg Sonnenberger
|
r47538 | self, text, sidedata, self._copiesstorage == b'changeset-sidedata' | ||
r43504 | ) | |||
mpm@selenic.com
|
r1089 | |||
Joerg Sonnenberger
|
r47376 | def readfiles(self, nodeorrev): | ||
Laurent Charignon
|
r27439 | """ | ||
short version of read that only returns the files modified by the cset | ||||
""" | ||||
Joerg Sonnenberger
|
r47376 | text = self.revision(nodeorrev) | ||
Laurent Charignon
|
r27439 | 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': | |||
r46320 | if files.has_copies_info: | |||
r46263 | 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) | ||||
Joerg Sonnenberger
|
r47258 | rev = self.addrevision( | ||
r46264 | text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags | |||
r43412 | ) | |||
Joerg Sonnenberger
|
r47258 | return self.node(rev) | ||
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.""" | ||||
Joerg Sonnenberger
|
r47082 | return self.changelogrevision(rev).branchinfo | ||
Boris Feld
|
r39923 | |||
Joerg Sonnenberger
|
r47259 | def _nodeduplicatecallback(self, transaction, rev): | ||
Boris Feld
|
r39923 | # 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', []) | ||
Joerg Sonnenberger
|
r47259 | duplicates.append(rev) | ||