--- a/.hgsigs Tue Jun 20 02:36:52 2023 +0200
+++ b/.hgsigs Sun Jun 18 00:09:39 2023 +0200
@@ -244,3 +244,4 @@
83ea6ce48b4fd09fb79c4e34cc5750c805699a53 0 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
--- a/.hgtags Tue Jun 20 02:36:52 2023 +0200
+++ b/.hgtags Sun Jun 18 00:09:39 2023 +0200
@@ -260,3 +260,4 @@
83ea6ce48b4fd09fb79c4e34cc5750c805699a53 6.4.1
f952be90b0514a576dcc8bbe758ce3847faba9bb 6.4.2
fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
+da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
--- a/hgext/clonebundles.py Tue Jun 20 02:36:52 2023 +0200
+++ b/hgext/clonebundles.py Sun Jun 18 00:09:39 2023 +0200
@@ -79,8 +79,10 @@
Bundle files can be generated with the :hg:`bundle` command. Typically
:hg:`bundle --all` is used to produce a bundle of the entire repository.
-:hg:`debugcreatestreamclonebundle` can be used to produce a special
-*streaming clonebundle*. These are bundle files that are extremely efficient
+The bundlespec option `stream` (see :hg:`help bundlespec`)
+can be used to produce a special *streaming clonebundle*, typically using
+:hg:`bundle --all --type="none-streamv2"`.
+These are bundle files that are extremely efficient
to produce and consume (read: fast). However, they are larger than
traditional bundle formats and require that clients support the exact set
of repository data store formats in use by the repository that created them.
--- a/mercurial/bundlecaches.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/bundlecaches.py Sun Jun 18 00:09:39 2023 +0200
@@ -52,6 +52,14 @@
return url
+SUPPORTED_CLONEBUNDLE_SCHEMES = [
+ b"http://",
+ b"https://",
+ b"largefile://",
+ CLONEBUNDLESCHEME,
+]
+
+
@attr.s
class bundlespec:
compression = attr.ib()
@@ -384,7 +392,9 @@
return False
-def filterclonebundleentries(repo, entries, streamclonerequested=False):
+def filterclonebundleentries(
+ repo, entries, streamclonerequested=False, pullbundles=False
+):
"""Remove incompatible clone bundle manifest entries.
Accepts a list of entries parsed with ``parseclonebundlesmanifest``
@@ -396,6 +406,16 @@
"""
newentries = []
for entry in entries:
+ url = entry.get(b'URL')
+ if not pullbundles and not any(
+ [url.startswith(scheme) for scheme in SUPPORTED_CLONEBUNDLE_SCHEMES]
+ ):
+ repo.ui.debug(
+ b'filtering %s because not a supported clonebundle scheme\n'
+ % url
+ )
+ continue
+
spec = entry.get(b'BUNDLESPEC')
if spec:
try:
@@ -405,8 +425,7 @@
# entries.
if streamclonerequested and not isstreamclonespec(bundlespec):
repo.ui.debug(
- b'filtering %s because not a stream clone\n'
- % entry[b'URL']
+ b'filtering %s because not a stream clone\n' % url
)
continue
@@ -416,7 +435,7 @@
except error.UnsupportedBundleSpecification as e:
repo.ui.debug(
b'filtering %s because unsupported bundle '
- b'spec: %s\n' % (entry[b'URL'], stringutil.forcebytestr(e))
+ b'spec: %s\n' % (url, stringutil.forcebytestr(e))
)
continue
# If we don't have a spec and requested a stream clone, we don't know
@@ -424,14 +443,12 @@
elif streamclonerequested:
repo.ui.debug(
b'filtering %s because cannot determine if a stream '
- b'clone bundle\n' % entry[b'URL']
+ b'clone bundle\n' % url
)
continue
if b'REQUIRESNI' in entry and not sslutil.hassni:
- repo.ui.debug(
- b'filtering %s because SNI not supported\n' % entry[b'URL']
- )
+ repo.ui.debug(b'filtering %s because SNI not supported\n' % url)
continue
if b'REQUIREDRAM' in entry:
@@ -439,15 +456,14 @@
requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
except error.ParseError:
repo.ui.debug(
- b'filtering %s due to a bad REQUIREDRAM attribute\n'
- % entry[b'URL']
+ b'filtering %s due to a bad REQUIREDRAM attribute\n' % url
)
continue
actualram = repo.ui.estimatememory()
if actualram is not None and actualram * 0.66 < requiredram:
repo.ui.debug(
b'filtering %s as it needs more than 2/3 of system memory\n'
- % entry[b'URL']
+ % url
)
continue
--- a/mercurial/dirstatemap.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/dirstatemap.py Sun Jun 18 00:09:39 2023 +0200
@@ -4,6 +4,7 @@
# GNU General Public License version 2 or any later version.
+import struct
from .i18n import _
from . import (
@@ -151,9 +152,15 @@
b'dirstate only has a docket in v2 format'
)
self._set_identity()
- self._docket = docketmod.DirstateDocket.parse(
- self._readdirstatefile(), self._nodeconstants
- )
+ try:
+ self._docket = docketmod.DirstateDocket.parse(
+ self._readdirstatefile(), self._nodeconstants
+ )
+ except struct.error:
+ self._ui.debug(b"failed to read dirstate-v2 data")
+ raise error.CorruptedDirstate(
+ b"failed to read dirstate-v2 data"
+ )
return self._docket
def _read_v2_data(self):
@@ -176,11 +183,23 @@
return self._opener.read(self.docket.data_filename())
def write_v2_no_append(self, tr, st, meta, packed):
- old_docket = self.docket
+ try:
+ old_docket = self.docket
+ except error.CorruptedDirstate:
+ # This means we've identified a dirstate-v1 file on-disk when we
+ # were expecting a dirstate-v2 docket. We've managed to recover
+ # from that unexpected situation, and now we want to write back a
+ # dirstate-v2 file to make the on-disk situation right again.
+ #
+ # This shouldn't be triggered since `self.docket` is cached and
+ # we would have called parents() or read() first, but it's here
+ # just in case.
+ old_docket = None
+
new_docket = docketmod.DirstateDocket.with_new_uuid(
self.parents(), len(packed), meta
)
- if old_docket.uuid == new_docket.uuid:
+ if old_docket is not None and old_docket.uuid == new_docket.uuid:
raise error.ProgrammingError(b'dirstate docket name collision')
data_filename = new_docket.data_filename()
self._opener.write(data_filename, packed)
@@ -194,7 +213,7 @@
st.close()
# Remove the old data file after the new docket pointing to
# the new data file was written.
- if old_docket.uuid:
+ if old_docket is not None and old_docket.uuid:
data_filename = old_docket.data_filename()
if tr is not None:
tr.addbackup(data_filename, location=b'plain')
@@ -211,28 +230,40 @@
def parents(self):
if not self._parents:
if self._use_dirstate_v2:
- self._parents = self.docket.parents
+ try:
+ self.docket
+ except error.CorruptedDirstate as e:
+ # fall back to dirstate-v1 if we fail to read v2
+ self._v1_parents(e)
+ else:
+ self._parents = self.docket.parents
else:
- read_len = self._nodelen * 2
- st = self._readdirstatefile(read_len)
- l = len(st)
- if l == read_len:
- self._parents = (
- st[: self._nodelen],
- st[self._nodelen : 2 * self._nodelen],
- )
- elif l == 0:
- self._parents = (
- self._nodeconstants.nullid,
- self._nodeconstants.nullid,
- )
- else:
- raise error.Abort(
- _(b'working directory state appears damaged!')
- )
+ self._v1_parents()
return self._parents
+ def _v1_parents(self, from_v2_exception=None):
+ read_len = self._nodelen * 2
+ st = self._readdirstatefile(read_len)
+ l = len(st)
+ if l == read_len:
+ self._parents = (
+ st[: self._nodelen],
+ st[self._nodelen : 2 * self._nodelen],
+ )
+ elif l == 0:
+ self._parents = (
+ self._nodeconstants.nullid,
+ self._nodeconstants.nullid,
+ )
+ else:
+ hint = None
+ if from_v2_exception is not None:
+ hint = _(b"falling back to dirstate-v1 from v2 also failed")
+ raise error.Abort(
+ _(b'working directory state appears damaged!'), hint
+ )
+
class dirstatemap(_dirstatemapcommon):
"""Map encapsulating the dirstate's contents.
@@ -330,11 +361,17 @@
def read(self):
testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
if self._use_dirstate_v2:
-
- if not self.docket.uuid:
- return
- testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
- st = self._read_v2_data()
+ try:
+ self.docket
+ except error.CorruptedDirstate:
+ # fall back to dirstate-v1 if we fail to read v2
+ self._set_identity()
+ st = self._readdirstatefile()
+ else:
+ if not self.docket.uuid:
+ return
+ testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
+ st = self._read_v2_data()
else:
self._set_identity()
st = self._readdirstatefile()
@@ -365,10 +402,17 @@
#
# (we cannot decorate the function directly since it is in a C module)
if self._use_dirstate_v2:
- p = self.docket.parents
- meta = self.docket.tree_metadata
- parse_dirstate = util.nogc(v2.parse_dirstate)
- parse_dirstate(self._map, self.copymap, st, meta)
+ try:
+ self.docket
+ except error.CorruptedDirstate:
+ # fall back to dirstate-v1 if we fail to parse v2
+ parse_dirstate = util.nogc(parsers.parse_dirstate)
+ p = parse_dirstate(self._map, self.copymap, st)
+ else:
+ p = self.docket.parents
+ meta = self.docket.tree_metadata
+ parse_dirstate = util.nogc(v2.parse_dirstate)
+ parse_dirstate(self._map, self.copymap, st, meta)
else:
parse_dirstate = util.nogc(parsers.parse_dirstate)
p = parse_dirstate(self._map, self.copymap, st)
@@ -597,38 +641,37 @@
testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
if self._use_dirstate_v2:
- self.docket # load the data if needed
- inode = (
- self.identity.stat.st_ino
- if self.identity is not None
- and self.identity.stat is not None
- else None
- )
- testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
- if not self.docket.uuid:
- data = b''
- self._map = rustmod.DirstateMap.new_empty()
+ try:
+ self.docket
+ except error.CorruptedDirstate as e:
+ # fall back to dirstate-v1 if we fail to read v2
+ parents = self._v1_map(e)
else:
- data = self._read_v2_data()
- self._map = rustmod.DirstateMap.new_v2(
- data,
- self.docket.data_size,
- self.docket.tree_metadata,
- self.docket.uuid,
- inode,
+ parents = self.docket.parents
+ inode = (
+ self.identity.stat.st_ino
+ if self.identity is not None
+ and self.identity.stat is not None
+ else None
+ )
+ testing.wait_on_cfg(
+ self._ui, b'dirstate.post-docket-read-file'
)
- parents = self.docket.parents
+ if not self.docket.uuid:
+ data = b''
+ self._map = rustmod.DirstateMap.new_empty()
+ else:
+ data = self._read_v2_data()
+ self._map = rustmod.DirstateMap.new_v2(
+ data,
+ self.docket.data_size,
+ self.docket.tree_metadata,
+ self.docket.uuid,
+ inode,
+ )
+ parents = self.docket.parents
else:
- self._set_identity()
- inode = (
- self.identity.stat.st_ino
- if self.identity is not None
- and self.identity.stat is not None
- else None
- )
- self._map, parents = rustmod.DirstateMap.new_v1(
- self._readdirstatefile(), inode
- )
+ parents = self._v1_map()
if parents and not self._dirtyparents:
self.setparents(*parents)
@@ -638,6 +681,23 @@
self.get = self._map.get
return self._map
+ def _v1_map(self, from_v2_exception=None):
+ self._set_identity()
+ inode = (
+ self.identity.stat.st_ino
+ if self.identity is not None and self.identity.stat is not None
+ else None
+ )
+ try:
+ self._map, parents = rustmod.DirstateMap.new_v1(
+ self._readdirstatefile(), inode
+ )
+ except OSError as e:
+ if from_v2_exception is not None:
+ raise e from from_v2_exception
+ raise
+ return parents
+
@property
def copymap(self):
return self._map.copymap()
@@ -696,9 +756,15 @@
self._dirtyparents = False
return
+ write_mode = self._write_mode
+ try:
+ docket = self.docket
+ except error.CorruptedDirstate:
+ # fall back to dirstate-v1 if we fail to parse v2
+ docket = None
+
# We can only append to an existing data file if there is one
- write_mode = self._write_mode
- if self.docket.uuid is None:
+ if docket is None or docket.uuid is None:
write_mode = WRITE_MODE_FORCE_NEW
packed, meta, append = self._map.write_v2(write_mode)
if append:
--- a/mercurial/error.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/error.py Sun Jun 18 00:09:39 2023 +0200
@@ -650,6 +650,13 @@
__bytes__ = _tobytes
+class CorruptedDirstate(Exception):
+ """error raised the dirstate appears corrupted on-disk. It may be due to
+ a dirstate version mismatch (i.e. expecting v2 and finding v1 on disk)."""
+
+ __bytes__ = _tobytes
+
+
class PeerTransportError(Abort):
"""Transport-level I/O error when communicating with a peer repo."""
--- a/mercurial/helptext/bundlespec.txt Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/helptext/bundlespec.txt Sun Jun 18 00:09:39 2023 +0200
@@ -67,6 +67,10 @@
.. bundlecompressionmarker
+The compression engines can be prepended with ``stream`` to create a streaming bundle.
+These are bundles that are extremely efficient to produce and consume,
+but do not have guaranteed compatibility with older clients.
+
Available Options
=================
@@ -89,7 +93,6 @@
revbranchcache
Include the "tags-fnodes" cache inside the bundle.
-
tagsfnodescache
Include the "tags-fnodes" cache inside the bundle.
@@ -109,3 +112,10 @@
``zstd-v1``
This errors because ``zstd`` is not supported for ``v1`` types.
+
+``none-streamv2``
+ Produce a ``v2`` streaming bundle with no compression.
+
+``zstd-v2;obsolescence=true;phases=true``
+ Produce a ``v2`` bundle with zstandard compression which includes
+ obsolescence markers and phases.
--- a/mercurial/revlog.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/revlog.py Sun Jun 18 00:09:39 2023 +0200
@@ -615,8 +615,8 @@
entry_point = b'%s.i.%s' % (self.radix, self.postfix)
elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
entry_point = b'%s.i.a' % self.radix
- elif self._try_split and self.opener.exists(b'%s.i.s' % self.radix):
- entry_point = b'%s.i.s' % self.radix
+ elif self._try_split and self.opener.exists(self._split_index_file):
+ entry_point = self._split_index_file
else:
entry_point = b'%s.i' % self.radix
@@ -2125,6 +2125,22 @@
raise error.CensoredNodeError(self.display_id, node, text)
raise
+ @property
+ def _split_index_file(self):
+ """the path where to expect the index of an ongoing splitting operation
+
+ The file will only exist if a splitting operation is in progress, but
+ it is always expected at the same location."""
+ parts = os.path.split(self.radix)
+ if len(parts) > 1:
+ # adds a '-s' prefix to the ``data/` or `meta/` base
+ head = parts[0] + b'-s'
+ return os.path.join(head, *parts[1:])
+ else:
+ # the revlog is stored at the root of the store (changelog or
+ # manifest), no risk of collision.
+ return self.radix + b'.i.s'
+
def _enforceinlinesize(self, tr, side_write=True):
"""Check if the revlog is too big for inline and convert if so.
@@ -2161,7 +2177,7 @@
# this code
if side_write:
old_index_file_path = self._indexfile
- new_index_file_path = self._indexfile + b'.s'
+ new_index_file_path = self._split_index_file
opener = self.opener
weak_self = weakref.ref(self)
--- a/mercurial/revlogutils/deltas.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/revlogutils/deltas.py Sun Jun 18 00:09:39 2023 +0200
@@ -1087,10 +1087,17 @@
):
self.revlog = revlog
self._write_debug = write_debug
- self._debug_search = debug_search
+ if write_debug is None:
+ self._debug_search = False
+ else:
+ self._debug_search = debug_search
self._debug_info = debug_info
self._snapshot_cache = SnapshotCache()
+ @property
+ def _gather_debug(self):
+ return self._write_debug is not None or self._debug_info is not None
+
def buildtext(self, revinfo, fh):
"""Builds a fulltext version of a revision
@@ -1136,7 +1143,6 @@
def _builddeltainfo(self, revinfo, base, fh, target_rev=None):
# can we use the cached delta?
revlog = self.revlog
- debug_search = self._write_debug is not None and self._debug_search
chainbase = revlog.chainbase(base)
if revlog._generaldelta:
deltabase = base
@@ -1173,7 +1179,7 @@
delta = revinfo.cachedelta[1]
if delta is None:
delta = self._builddeltadiff(base, revinfo, fh)
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: uncompressed-delta-size=%d\n"
msg %= len(delta)
self._write_debug(msg)
@@ -1181,17 +1187,17 @@
if revlog.upperboundcomp is not None and snapshotdepth:
lowestrealisticdeltalen = len(delta) // revlog.upperboundcomp
snapshotlimit = revinfo.textlen >> snapshotdepth
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: projected-lower-size=%d\n"
msg %= lowestrealisticdeltalen
self._write_debug(msg)
if snapshotlimit < lowestrealisticdeltalen:
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: DISCARDED (snapshot limit)\n"
self._write_debug(msg)
return None
if revlog.length(base) < lowestrealisticdeltalen:
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: DISCARDED (prev size)\n"
self._write_debug(msg)
return None
@@ -1253,41 +1259,34 @@
if target_rev is None:
target_rev = len(self.revlog)
- if not revinfo.textlen:
- return self._fullsnapshotinfo(fh, revinfo, target_rev)
+ gather_debug = self._gather_debug
+ cachedelta = revinfo.cachedelta
+ revlog = self.revlog
+ p1r = p2r = None
if excluded_bases is None:
excluded_bases = set()
- # no delta for flag processor revision (see "candelta" for why)
- # not calling candelta since only one revision needs test, also to
- # avoid overhead fetching flags again.
- if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
- return self._fullsnapshotinfo(fh, revinfo, target_rev)
-
- gather_debug = (
- self._write_debug is not None or self._debug_info is not None
- )
- debug_search = self._write_debug is not None and self._debug_search
-
if gather_debug:
start = util.timer()
-
- # count the number of different delta we tried (for debug purpose)
- dbg_try_count = 0
- # count the number of "search round" we did. (for debug purpose)
- dbg_try_rounds = 0
- dbg_type = b'unknown'
-
- cachedelta = revinfo.cachedelta
- p1 = revinfo.p1
- p2 = revinfo.p2
- revlog = self.revlog
-
- deltainfo = None
- p1r, p2r = revlog.rev(p1), revlog.rev(p2)
-
- if gather_debug:
+ dbg = self._one_dbg_data()
+ dbg['revision'] = target_rev
+ target_revlog = b"UNKNOWN"
+ target_type = self.revlog.target[0]
+ target_key = self.revlog.target[1]
+ if target_type == KIND_CHANGELOG:
+ target_revlog = b'CHANGELOG:'
+ elif target_type == KIND_MANIFESTLOG:
+ target_revlog = b'MANIFESTLOG:'
+ if target_key:
+ target_revlog += b'%s:' % target_key
+ elif target_type == KIND_FILELOG:
+ target_revlog = b'FILELOG:'
+ if target_key:
+ target_revlog += b'%s:' % target_key
+ dbg['target-revlog'] = target_revlog
+ p1r = revlog.rev(revinfo.p1)
+ p2r = revlog.rev(revinfo.p2)
if p1r != nullrev:
p1_chain_len = revlog._chaininfo(p1r)[0]
else:
@@ -1296,7 +1295,109 @@
p2_chain_len = revlog._chaininfo(p2r)[0]
else:
p2_chain_len = -1
- if debug_search:
+ dbg['p1-chain-len'] = p1_chain_len
+ dbg['p2-chain-len'] = p2_chain_len
+
+ # 1) if the revision is empty, no amount of delta can beat it
+ #
+ # 2) no delta for flag processor revision (see "candelta" for why)
+ # not calling candelta since only one revision needs test, also to
+ # avoid overhead fetching flags again.
+ if not revinfo.textlen or revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
+ deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev)
+ if gather_debug:
+ end = util.timer()
+ dbg['duration'] = end - start
+ dbg[
+ 'delta-base'
+ ] = deltainfo.base # pytype: disable=attribute-error
+ dbg['search_round_count'] = 0
+ dbg['using-cached-base'] = False
+ dbg['delta_try_count'] = 0
+ dbg['type'] = b"full"
+ dbg['snapshot-depth'] = 0
+ self._dbg_process_data(dbg)
+ return deltainfo
+
+ deltainfo = None
+
+ # If this source delta are to be forcibly reuse, let us comply early.
+ if (
+ revlog._generaldelta
+ and revinfo.cachedelta is not None
+ and revinfo.cachedelta[2] == DELTA_BASE_REUSE_FORCE
+ ):
+ base = revinfo.cachedelta[0]
+ if base == nullrev:
+ dbg_type = b"full"
+ deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev)
+ if gather_debug:
+ snapshotdepth = 0
+ elif base not in excluded_bases:
+ delta = revinfo.cachedelta[1]
+ header, data = revlog.compress(delta)
+ deltalen = len(header) + len(data)
+ if gather_debug:
+ offset = revlog.end(len(revlog) - 1)
+ chainbase = revlog.chainbase(base)
+ distance = deltalen + offset - revlog.start(chainbase)
+ chainlen, compresseddeltalen = revlog._chaininfo(base)
+ chainlen += 1
+ compresseddeltalen += deltalen
+ if base == p1r or base == p2r:
+ dbg_type = b"delta"
+ snapshotdepth = None
+ elif not revlog.issnapshot(base):
+ snapshotdepth = None
+ else:
+ dbg_type = b"snapshot"
+ snapshotdepth = revlog.snapshotdepth(base) + 1
+ else:
+ distance = None
+ chainbase = None
+ chainlen = None
+ compresseddeltalen = None
+ snapshotdepth = None
+ deltainfo = _deltainfo(
+ distance=distance,
+ deltalen=deltalen,
+ data=(header, data),
+ base=base,
+ chainbase=chainbase,
+ chainlen=chainlen,
+ compresseddeltalen=compresseddeltalen,
+ snapshotdepth=snapshotdepth,
+ )
+
+ if deltainfo is not None:
+ if gather_debug:
+ end = util.timer()
+ dbg['duration'] = end - start
+ dbg[
+ 'delta-base'
+ ] = deltainfo.base # pytype: disable=attribute-error
+ dbg['search_round_count'] = 0
+ dbg['using-cached-base'] = True
+ dbg['delta_try_count'] = 0
+ dbg['type'] = b"full"
+ if snapshotdepth is None:
+ dbg['snapshot-depth'] = 0
+ else:
+ dbg['snapshot-depth'] = snapshotdepth
+ self._dbg_process_data(dbg)
+ return deltainfo
+
+ # count the number of different delta we tried (for debug purpose)
+ dbg_try_count = 0
+ # count the number of "search round" we did. (for debug purpose)
+ dbg_try_rounds = 0
+ dbg_type = b'unknown'
+
+ if p1r is None:
+ p1r = revlog.rev(revinfo.p1)
+ p2r = revlog.rev(revinfo.p2)
+
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: SEARCH rev=%d\n"
msg %= target_rev
self._write_debug(msg)
@@ -1314,7 +1415,7 @@
candidaterevs = next(groups)
while candidaterevs is not None:
dbg_try_rounds += 1
- if debug_search:
+ if self._debug_search:
prev = None
if deltainfo is not None:
prev = deltainfo.base
@@ -1325,7 +1426,7 @@
and cachedelta[0] in candidaterevs
):
round_type = b"cached-delta"
- elif p1 in candidaterevs or p2 in candidaterevs:
+ elif p1r in candidaterevs or p2r in candidaterevs:
round_type = b"parents"
elif prev is not None and all(c < prev for c in candidaterevs):
round_type = b"refine-down"
@@ -1338,7 +1439,7 @@
self._write_debug(msg)
nominateddeltas = []
if deltainfo is not None:
- if debug_search:
+ if self._debug_search:
msg = (
b"DBG-DELTAS-SEARCH: CONTENDER: rev=%d - length=%d\n"
)
@@ -1348,14 +1449,14 @@
# challenge it against refined candidates
nominateddeltas.append(deltainfo)
for candidaterev in candidaterevs:
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: CANDIDATE: rev=%d\n"
msg %= candidaterev
self._write_debug(msg)
candidate_type = None
- if candidaterev == p1:
+ if candidaterev == p1r:
candidate_type = b"p1"
- elif candidaterev == p2:
+ elif candidaterev == p2r:
candidate_type = b"p2"
elif self.revlog.issnapshot(candidaterev):
candidate_type = b"snapshot-%d"
@@ -1376,7 +1477,7 @@
dbg_try_count += 1
- if debug_search:
+ if self._debug_search:
delta_start = util.timer()
candidatedelta = self._builddeltainfo(
revinfo,
@@ -1384,23 +1485,23 @@
fh,
target_rev=target_rev,
)
- if debug_search:
+ if self._debug_search:
delta_end = util.timer()
msg = b"DBG-DELTAS-SEARCH: delta-search-time=%f\n"
msg %= delta_end - delta_start
self._write_debug(msg)
if candidatedelta is not None:
if is_good_delta_info(self.revlog, candidatedelta, revinfo):
- if debug_search:
+ if self._debug_search:
msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (GOOD)\n"
msg %= candidatedelta.deltalen
self._write_debug(msg)
nominateddeltas.append(candidatedelta)
- elif debug_search:
+ elif self._debug_search:
msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (BAD)\n"
msg %= candidatedelta.deltalen
self._write_debug(msg)
- elif debug_search:
+ elif self._debug_search:
msg = b"DBG-DELTAS-SEARCH: NO-DELTA\n"
self._write_debug(msg)
if nominateddeltas:
@@ -1434,17 +1535,14 @@
and dbg_try_count == 1
and deltainfo.base == cachedelta[0]
)
- dbg = {
- 'duration': end - start,
- 'revision': target_rev,
- 'delta-base': deltainfo.base, # pytype: disable=attribute-error
- 'search_round_count': dbg_try_rounds,
- 'using-cached-base': used_cached,
- 'delta_try_count': dbg_try_count,
- 'type': dbg_type,
- 'p1-chain-len': p1_chain_len,
- 'p2-chain-len': p2_chain_len,
- }
+ dbg['duration'] = end - start
+ dbg[
+ 'delta-base'
+ ] = deltainfo.base # pytype: disable=attribute-error
+ dbg['search_round_count'] = dbg_try_rounds
+ dbg['using-cached-base'] = used_cached
+ dbg['delta_try_count'] = dbg_try_count
+ dbg['type'] = dbg_type
if (
deltainfo.snapshotdepth # pytype: disable=attribute-error
is not None
@@ -1454,55 +1552,58 @@
] = deltainfo.snapshotdepth # pytype: disable=attribute-error
else:
dbg['snapshot-depth'] = 0
- target_revlog = b"UNKNOWN"
- target_type = self.revlog.target[0]
- target_key = self.revlog.target[1]
- if target_type == KIND_CHANGELOG:
- target_revlog = b'CHANGELOG:'
- elif target_type == KIND_MANIFESTLOG:
- target_revlog = b'MANIFESTLOG:'
- if target_key:
- target_revlog += b'%s:' % target_key
- elif target_type == KIND_FILELOG:
- target_revlog = b'FILELOG:'
- if target_key:
- target_revlog += b'%s:' % target_key
- dbg['target-revlog'] = target_revlog
+ self._dbg_process_data(dbg)
+ return deltainfo
- if self._debug_info is not None:
- self._debug_info.append(dbg)
+ def _one_dbg_data(self):
+ return {
+ 'duration': None,
+ 'revision': None,
+ 'delta-base': None,
+ 'search_round_count': None,
+ 'using-cached-base': None,
+ 'delta_try_count': None,
+ 'type': None,
+ 'p1-chain-len': None,
+ 'p2-chain-len': None,
+ 'snapshot-depth': None,
+ 'target-revlog': None,
+ }
+
+ def _dbg_process_data(self, dbg):
+ if self._debug_info is not None:
+ self._debug_info.append(dbg)
- if self._write_debug is not None:
- msg = (
- b"DBG-DELTAS:"
- b" %-12s"
- b" rev=%d:"
- b" delta-base=%d"
- b" is-cached=%d"
- b" - search-rounds=%d"
- b" try-count=%d"
- b" - delta-type=%-6s"
- b" snap-depth=%d"
- b" - p1-chain-length=%d"
- b" p2-chain-length=%d"
- b" - duration=%f"
- b"\n"
- )
- msg %= (
- dbg["target-revlog"],
- dbg["revision"],
- dbg["delta-base"],
- dbg["using-cached-base"],
- dbg["search_round_count"],
- dbg["delta_try_count"],
- dbg["type"],
- dbg["snapshot-depth"],
- dbg["p1-chain-len"],
- dbg["p2-chain-len"],
- dbg["duration"],
- )
- self._write_debug(msg)
- return deltainfo
+ if self._write_debug is not None:
+ msg = (
+ b"DBG-DELTAS:"
+ b" %-12s"
+ b" rev=%d:"
+ b" delta-base=%d"
+ b" is-cached=%d"
+ b" - search-rounds=%d"
+ b" try-count=%d"
+ b" - delta-type=%-6s"
+ b" snap-depth=%d"
+ b" - p1-chain-length=%d"
+ b" p2-chain-length=%d"
+ b" - duration=%f"
+ b"\n"
+ )
+ msg %= (
+ dbg["target-revlog"],
+ dbg["revision"],
+ dbg["delta-base"],
+ dbg["using-cached-base"],
+ dbg["search_round_count"],
+ dbg["delta_try_count"],
+ dbg["type"],
+ dbg["snapshot-depth"],
+ dbg["p1-chain-len"],
+ dbg["p2-chain-len"],
+ dbg["duration"],
+ )
+ self._write_debug(msg)
def delta_compression(default_compression_header, deltainfo):
--- a/mercurial/templatefuncs.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/templatefuncs.py Sun Jun 18 00:09:39 2023 +0200
@@ -50,8 +50,8 @@
@templatefunc(b'date(date[, fmt])')
def date(context, mapping, args):
- """Format a date. See :hg:`help dates` for formatting
- strings. The default is a Unix date format, including the timezone:
+ """Format a date. The format string uses the Python strftime format.
+ The default is a Unix date format, including the timezone:
"Mon Sep 04 15:13:13 2006 0700"."""
if not (1 <= len(args) <= 2):
# i18n: "date" is a keyword
--- a/mercurial/transaction.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/transaction.py Sun Jun 18 00:09:39 2023 +0200
@@ -290,6 +290,8 @@
self._backupjournal = b"%s.backupfiles" % self._journal
self._backupsfile = opener.open(self._backupjournal, b'w')
self._backupsfile.write(b'%d\n' % version)
+ # the set of temporary files
+ self._tmp_files = set()
if createmode is not None:
opener.chmod(self._journal, createmode & 0o666)
@@ -354,6 +356,7 @@
file in self._newfiles
or file in self._offsetmap
or file in self._backupmap
+ or file in self._tmp_files
):
return
if self._queue:
@@ -368,6 +371,7 @@
file in self._newfiles
or file in self._offsetmap
or file in self._backupmap
+ or file in self._tmp_files
):
return
if offset:
@@ -439,6 +443,7 @@
Such files will be deleted when the transaction exits (on both
failure and success).
"""
+ self._tmp_files.add(tmpfile)
self._addbackupentry((location, b'', tmpfile, False))
@active
--- a/mercurial/wireprotov1server.py Tue Jun 20 02:36:52 2023 +0200
+++ b/mercurial/wireprotov1server.py Sun Jun 18 00:09:39 2023 +0200
@@ -437,7 +437,7 @@
if not manifest:
return None
res = bundlecaches.parseclonebundlesmanifest(repo, manifest)
- res = bundlecaches.filterclonebundleentries(repo, res)
+ res = bundlecaches.filterclonebundleentries(repo, res, pullbundles=True)
if not res:
return None
cl = repo.unfiltered().changelog
--- a/relnotes/6.4 Tue Jun 20 02:36:52 2023 +0200
+++ b/relnotes/6.4 Sun Jun 18 00:09:39 2023 +0200
@@ -1,3 +1,14 @@
+= Mercurial 6.4.4 =
+
+ * clonebundles: filter out invalid schemes instead of failing on them
+ * doc: format argument for date uses strftime format string (issue6818)
+ * test: make test-contrib-perf.t more robust
+ * revlog: fix a bug in revlog splitting
+ * bundles: clarify streaming v2 bundle usage
+ * delta-find: fix pulled-delta-reuse-policy=forced behavior
+ * dirstate: fall back to v1 if reading v2 failed
+ * revlog: avoid possible collision between directory and temporary index
+
= Mercurial 6.4.3 =
* chg: declare environ (issue6812)
--- a/rust/hg-core/src/repo.rs Tue Jun 20 02:36:52 2023 +0200
+++ b/rust/hg-core/src/repo.rs Sun Jun 18 00:09:39 2023 +0200
@@ -232,7 +232,17 @@
try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
}
- pub fn has_dirstate_v2(&self) -> bool {
+ /// Whether this repo should use dirstate-v2.
+ /// The presence of `dirstate-v2` in the requirements does not mean that
+ /// the on-disk dirstate is necessarily in version 2. In most cases,
+ /// a dirstate-v2 file will indeed be found, but in rare cases (like the
+ /// upgrade mechanism being cut short), the on-disk version will be a
+ /// v1 file.
+ /// Semantically, having a requirement only means that a client cannot
+ /// properly understand or properly update the repo if it lacks the support
+ /// for the required feature, but not that that feature is actually used
+ /// in all occasions.
+ pub fn use_dirstate_v2(&self) -> bool {
self.requirements
.contains(requirements::DIRSTATE_V2_REQUIREMENT)
}
@@ -277,10 +287,21 @@
let dirstate = self.dirstate_file_contents()?;
let parents = if dirstate.is_empty() {
DirstateParents::NULL
- } else if self.has_dirstate_v2() {
- let docket =
- crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
- docket.parents()
+ } else if self.use_dirstate_v2() {
+ let docket_res =
+ crate::dirstate_tree::on_disk::read_docket(&dirstate);
+ match docket_res {
+ Ok(docket) => docket.parents(),
+ Err(_) => {
+ log::info!(
+ "Parsing dirstate docket failed, \
+ falling back to dirstate-v1"
+ );
+ *crate::dirstate::parsers::parse_dirstate_parents(
+ &dirstate,
+ )?
+ }
+ }
} else {
*crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
};
@@ -296,7 +317,7 @@
&self,
) -> Result<DirstateMapIdentity, HgError> {
assert!(
- self.has_dirstate_v2(),
+ self.use_dirstate_v2(),
"accessing dirstate data file ID without dirstate-v2"
);
// Get the identity before the contents since we could have a race
@@ -308,15 +329,35 @@
self.dirstate_parents.set(DirstateParents::NULL);
Ok((identity, None, 0))
} else {
- let docket =
- crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
- self.dirstate_parents.set(docket.parents());
- Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
+ let docket_res =
+ crate::dirstate_tree::on_disk::read_docket(&dirstate);
+ match docket_res {
+ Ok(docket) => {
+ self.dirstate_parents.set(docket.parents());
+ Ok((
+ identity,
+ Some(docket.uuid.to_owned()),
+ docket.data_size(),
+ ))
+ }
+ Err(_) => {
+ log::info!(
+ "Parsing dirstate docket failed, \
+ falling back to dirstate-v1"
+ );
+ let parents =
+ *crate::dirstate::parsers::parse_dirstate_parents(
+ &dirstate,
+ )?;
+ self.dirstate_parents.set(parents);
+ Ok((identity, None, 0))
+ }
+ }
}
}
fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
- if self.has_dirstate_v2() {
+ if self.use_dirstate_v2() {
// The v2 dirstate is split into a docket and a data file.
// Since we don't always take the `wlock` to read it
// (like in `hg status`), it is susceptible to races.
@@ -343,7 +384,13 @@
);
continue;
}
- _ => return Err(e),
+ _ => {
+ log::info!(
+ "Reading dirstate v2 failed, \
+ falling back to v1"
+ );
+ return self.new_dirstate_map_v1();
+ }
},
}
}
@@ -354,23 +401,22 @@
);
Err(DirstateError::Common(error))
} else {
- debug_wait_for_file_or_print(
- self.config(),
- "dirstate.pre-read-file",
- );
- let identity = self.dirstate_identity()?;
- let dirstate_file_contents = self.dirstate_file_contents()?;
- if dirstate_file_contents.is_empty() {
- self.dirstate_parents.set(DirstateParents::NULL);
- Ok(OwningDirstateMap::new_empty(Vec::new()))
- } else {
- let (map, parents) = OwningDirstateMap::new_v1(
- dirstate_file_contents,
- identity,
- )?;
- self.dirstate_parents.set(parents);
- Ok(map)
- }
+ self.new_dirstate_map_v1()
+ }
+ }
+
+ fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> {
+ debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
+ let identity = self.dirstate_identity()?;
+ let dirstate_file_contents = self.dirstate_file_contents()?;
+ if dirstate_file_contents.is_empty() {
+ self.dirstate_parents.set(DirstateParents::NULL);
+ Ok(OwningDirstateMap::new_empty(Vec::new()))
+ } else {
+ let (map, parents) =
+ OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
+ self.dirstate_parents.set(parents);
+ Ok(map)
}
}
@@ -550,7 +596,7 @@
// TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
// it’s unset
let parents = self.dirstate_parents()?;
- let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
+ let (packed_dirstate, old_uuid_to_remove) = if self.use_dirstate_v2() {
let (identity, uuid, data_size) =
self.get_dirstate_data_file_integrity()?;
let identity_changed = identity != map.old_identity();
--- a/tests/test-clonebundles.t Tue Jun 20 02:36:52 2023 +0200
+++ b/tests/test-clonebundles.t Sun Jun 18 00:09:39 2023 +0200
@@ -59,6 +59,20 @@
(if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false")
[255]
+Manifest file with URL with unknown scheme skips the URL
+ $ echo 'weirdscheme://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest
+ $ hg clone http://localhost:$HGPORT unknown-scheme
+ no compatible clone bundles available on server; falling back to regular clone
+ (you may want to report this to the server operator)
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 2 changes to 2 files
+ new changesets 53245c60e682:aaff8d2ffbbf
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
Server is not running aborts
$ echo "http://localhost:$HGPORT1/bundle.hg" > server/.hg/clonebundles.manifest
--- a/tests/test-contrib-perf.t Tue Jun 20 02:36:52 2023 +0200
+++ b/tests/test-contrib-perf.t Sun Jun 18 00:09:39 2023 +0200
@@ -304,20 +304,20 @@
Multiple entries
- $ hg perfparents --config perf.stub=no --config perf.run-limits='500000-1, 0.000000001-5'
- ! wall * comb * user * sys * (best of 5) (glob)
+ $ hg perfparents --config perf.stub=no --config perf.run-limits='500000-1, 0.000000001-50'
+ ! wall * comb * user * sys * (best of 50) (glob)
error case are ignored
- $ hg perfparents --config perf.stub=no --config perf.run-limits='500, 0.000000001-5'
+ $ hg perfparents --config perf.stub=no --config perf.run-limits='500, 0.000000001-50'
malformatted run limit entry, missing "-": 500
- ! wall * comb * user * sys * (best of 5) (glob)
- $ hg perfparents --config perf.stub=no --config perf.run-limits='aaa-12, 0.000000001-5'
- malformatted run limit entry, could not convert string to float: 'aaa': aaa-12
- ! wall * comb * user * sys * (best of 5) (glob)
- $ hg perfparents --config perf.stub=no --config perf.run-limits='12-aaaaaa, 0.000000001-5'
- malformatted run limit entry, invalid literal for int() with base 10: 'aaaaaa': 12-aaaaaa
- ! wall * comb * user * sys * (best of 5) (glob)
+ ! wall * comb * user * sys * (best of 50) (glob)
+ $ hg perfparents --config perf.stub=no --config perf.run-limits='aaa-120, 0.000000001-50'
+ malformatted run limit entry, could not convert string to float: 'aaa': aaa-120
+ ! wall * comb * user * sys * (best of 50) (glob)
+ $ hg perfparents --config perf.stub=no --config perf.run-limits='120-aaaaaa, 0.000000001-50'
+ malformatted run limit entry, invalid literal for int() with base 10: 'aaaaaa': 120-aaaaaa
+ ! wall * comb * user * sys * (best of 50) (glob)
test actual output
------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-dirstate-version-fallback.t Sun Jun 18 00:09:39 2023 +0200
@@ -0,0 +1,51 @@
+ $ cat >> $HGRCPATH << EOF
+ > [storage]
+ > dirstate-v2.slow-path=allow
+ > [format]
+ > use-dirstate-v2=no
+ > EOF
+
+Set up a v1 repo
+
+ $ hg init repo
+ $ cd repo
+ $ echo a > a
+ $ hg add a
+ $ hg commit -m a
+ $ hg debugrequires | grep dirstate
+ [1]
+ $ ls -1 .hg/dirstate*
+ .hg/dirstate
+
+Copy v1 dirstate
+ $ cp .hg/dirstate $TESTTMP/dirstate-v1-backup
+
+Upgrade it to v2
+
+ $ hg debugupgraderepo -q --config format.use-dirstate-v2=1 --run | egrep 'added:|removed:'
+ added: dirstate-v2
+ $ hg debugrequires | grep dirstate
+ dirstate-v2
+ $ ls -1 .hg/dirstate*
+ .hg/dirstate
+ .hg/dirstate.* (glob)
+
+Manually reset to dirstate v1 to simulate an incomplete dirstate-v2 upgrade
+
+ $ rm .hg/dirstate*
+ $ cp $TESTTMP/dirstate-v1-backup .hg/dirstate
+
+There should be no errors, but a v2 dirstate should be written back to disk
+ $ hg st
+ $ ls -1 .hg/dirstate*
+ .hg/dirstate
+ .hg/dirstate.* (glob)
+
+Corrupt the dirstate to see how the errors show up to the user
+ $ echo "I ate your data" > .hg/dirstate
+
+ $ hg st
+ abort: working directory state appears damaged! (no-rhg !)
+ (falling back to dirstate-v1 from v2 also failed) (no-rhg !)
+ abort: Too little data for dirstate: 16 bytes. (rhg !)
+ [255]
--- a/tests/test-revlog-delta-find.t Tue Jun 20 02:36:52 2023 +0200
+++ b/tests/test-revlog-delta-find.t Sun Jun 18 00:09:39 2023 +0200
@@ -329,5 +329,34 @@
DBG-DELTAS: CHANGELOG: * (glob)
DBG-DELTAS: MANIFESTLOG: * (glob)
DBG-DELTAS: MANIFESTLOG: * (glob)
- DBG-DELTAS: FILELOG:my-file.txt: rev=3: delta-base=2 * (glob)
- DBG-DELTAS: FILELOG:my-file.txt: rev=4: delta-base=3 * (glob)
+ DBG-DELTAS: FILELOG:my-file.txt: rev=3: delta-base=2 is-cached=1 *search-rounds=0 try-count=0* (glob)
+ DBG-DELTAS: FILELOG:my-file.txt: rev=4: delta-base=3 is-cached=1 *search-rounds=0 try-count=0* (glob)
+
+Check that running "forced" on a non-general delta repository does not corrupt it
+---------------------------------------------------------------------------------
+
+Even if requested to be used, some of the delta in the revlog cannot be stored on a non-general delta repository. We check that the bundle application was correct.
+
+ $ hg init \
+ > --config format.usegeneraldelta=no \
+ > --config format.sparse-revlog=no \
+ > local-forced-full-p1-no-gd
+ $ hg debugformat -R local-forced-full-p1-no-gd | grep generaldelta
+ generaldelta: no
+ $ hg -R local-forced-full-p1-no-gd pull --quiet local-pre-pull-full \
+ > --config debug.revlog.debug-delta=no
+ $ hg -R local-forced-full-p1-no-gd pull --quiet \
+ > --config 'paths.*:pulled-delta-reuse-policy=forced' all-p1.hg
+ DBG-DELTAS: CHANGELOG: * (glob)
+ DBG-DELTAS: CHANGELOG: * (glob)
+ DBG-DELTAS: MANIFESTLOG: * (glob)
+ DBG-DELTAS: MANIFESTLOG: * (glob)
+ DBG-DELTAS: FILELOG:my-file.txt: rev=3: delta-base=0 * - search-rounds=1 try-count=1 * (glob)
+ DBG-DELTAS: FILELOG:my-file.txt: rev=4: delta-base=4 * - search-rounds=1 try-count=1 * (glob)
+ $ hg -R local-forced-full-p1-no-gd verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ checking dirstate
+ checked 5 changesets with 5 changes to 1 files
--- a/tests/test-transaction-rollback-on-revlog-split.t Tue Jun 20 02:36:52 2023 +0200
+++ b/tests/test-transaction-rollback-on-revlog-split.t Sun Jun 18 00:09:39 2023 +0200
@@ -84,6 +84,8 @@
> Directory_With,Special%Char/Complex_File.babar
> foo/bar/babar_celeste/foo
> 1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/f
+ > some_dir/sub_dir/foo_bar
+ > some_dir/sub_dir/foo_bar.i.s/tutu
> "
$ for f in $files; do
> mkdir -p `dirname $f`
@@ -104,13 +106,17 @@
> dd if=/dev/zero of=$f bs=1k count=128 > /dev/null 2>&1
> done
$ hg commit -AqmD --traceback
+ $ for f in $files; do
+ > dd if=/dev/zero of=$f bs=1k count=132 > /dev/null 2>&1
+ > done
+ $ hg commit -AqmD --traceback
Reference size:
$ f -s file
- file: size=131072
- $ f -s .hg/store/data/file*
- .hg/store/data/file.d: size=132139
- .hg/store/data/file.i: size=256
+ file: size=135168
+ $ f -s .hg/store/data*/file*
+ .hg/store/data/file.d: size=267307
+ .hg/store/data/file.i: size=320
$ cd ..
@@ -134,16 +140,16 @@
adding changesets
adding manifests
adding file changes
- added 2 changesets with 8 changes to 4 files
- new changesets 16a630ece54e:8437c461d70a
+ added 3 changesets with 18 changes to 6 files
+ new changesets c99a94cae9b1:64874a3b0160
(run 'hg update' to get a working copy)
The inline revlog has been replaced
$ f -s .hg/store/data/file*
- .hg/store/data/file.d: size=132139
- .hg/store/data/file.i: size=256
+ .hg/store/data/file.d: size=267307
+ .hg/store/data/file.i: size=320
$ hg verify -q
@@ -171,7 +177,7 @@
Reference size:
$ f -s file
file: size=1024
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ cat > .hg/hgrc <<EOF
@@ -192,10 +198,13 @@
The inline revlog still exist, but a split version exist next to it
- $ f -s .hg/store/data/file*
- .hg/store/data/file.d: size=132139
+ $ cat .hg/store/journal | tr '\0' ' ' | grep '\.s'
+ data/some_dir/sub_dir/foo_bar.i.s/tutu.i 1174
+ data/some_dir/sub_dir/foo_bar.i.s/tutu.d 0
+ $ f -s .hg/store/data*/file*
+ .hg/store/data-s/file: size=320
+ .hg/store/data/file.d: size=267307
.hg/store/data/file.i: size=132395
- .hg/store/data/file.i.s: size=256
The first file.i entry should match the "Reference size" above.
@@ -206,19 +215,19 @@
$ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
data/file.i 1174
data/file.d 0
- $ cat .hg/store/journal.backupfiles | tr -s '\000' ' ' | tr -s '\00' ' '| grep data/file
+ $ cat .hg/store/journal.backupfiles | tr -s '\000' ' ' | tr -s '\00' ' '| grep 'data.*/file'
data/file.i data/journal.backup.file.i.bck 0
- data/file.i.s 0
+ data-s/file 0
recover is rolling the split back, the fncache is still valid
$ hg recover
rolling back interrupted transaction
(verify step skipped, run `hg verify` to check your repository content)
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ hg tip
- changeset: 1:cc8dfb126534
+ changeset: 1:64b04c8dc267
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -243,7 +252,7 @@
Reference size:
$ f -s file
file: size=1024
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ cat > .hg/hgrc <<EOF
@@ -271,12 +280,12 @@
The inline revlog still exist, but a split version exist next to it
- $ f -s .hg/store/data/file*
- .hg/store/data/file.d: size=132139
+ $ f -s .hg/store/data*/file*
+ .hg/store/data-s/file: size=320
+ .hg/store/data/file.d: size=267307
.hg/store/data/file.i: size=132395
- .hg/store/data/file.i.s: size=256
- $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
+ $ cat .hg/store/journal | tr -s '\000' ' ' | grep 'data.*/file'
data/file.i 1174
data/file.d 0
@@ -285,10 +294,10 @@
$ hg recover
rolling back interrupted transaction
(verify step skipped, run `hg verify` to check your repository content)
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ hg tip
- changeset: 1:cc8dfb126534
+ changeset: 1:64b04c8dc267
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -308,7 +317,7 @@
Reference size:
$ f -s file
file: size=1024
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ cat > .hg/hgrc <<EOF
@@ -336,11 +345,11 @@
The inline revlog was over written on disk
- $ f -s .hg/store/data/file*
- .hg/store/data/file.d: size=132139
- .hg/store/data/file.i: size=256
+ $ f -s .hg/store/data*/file*
+ .hg/store/data/file.d: size=267307
+ .hg/store/data/file.i: size=320
- $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
+ $ cat .hg/store/journal | tr -s '\000' ' ' | grep 'data.*/file'
data/file.i 1174
data/file.d 0
@@ -349,10 +358,10 @@
$ hg recover
rolling back interrupted transaction
(verify step skipped, run `hg verify` to check your repository content)
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.i: size=1174
$ hg tip
- changeset: 1:cc8dfb126534
+ changeset: 1:64b04c8dc267
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -386,13 +395,13 @@
The split was rollback
- $ f -s .hg/store/data/file*
+ $ f -s .hg/store/data*/file*
.hg/store/data/file.d: size=0
.hg/store/data/file.i: size=1174
$ hg tip
- changeset: 1:cc8dfb126534
+ changeset: 1:64b04c8dc267
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
@@ -472,7 +481,7 @@
adding changesets
adding manifests
adding file changes
- size=131072
+ size=135168
transaction abort!
rollback completed
abort: pretxnclose.03-abort hook exited with status 1
--- a/tests/test-upgrade-repo.t Tue Jun 20 02:36:52 2023 +0200
+++ b/tests/test-upgrade-repo.t Sun Jun 18 00:09:39 2023 +0200
@@ -839,6 +839,7 @@
00changelog.i
00manifest.i
data
+ data-s
fncache
phaseroots
requires
@@ -862,6 +863,7 @@
00changelog.i
00manifest.i
data
+ data-s
fncache
phaseroots
requires