diff --git a/.hgsigs b/.hgsigs
index a232522d377076537ae60536fe868b1623e9515d_Lmhnc2lncw==..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_Lmhnc2lncw== 100644
--- a/.hgsigs
+++ b/.hgsigs
@@ -273,3 +273,4 @@
 b964f92261d4fbb64f19aa6af2b072f7730b913a 0 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
+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
diff --git a/.hgtags b/.hgtags
index a232522d377076537ae60536fe868b1623e9515d_LmhndGFncw==..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_LmhndGFncw== 100644
--- a/.hgtags
+++ b/.hgtags
@@ -289,3 +289,4 @@
 b964f92261d4fbb64f19aa6af2b072f7730b913a 6.9.2
 89ab2459f62ac8da3eb4f3ee2120d1814ce805d5 6.9.3
 ed68f9a47a090cc6450a7d5617baf123ba5fc42a 7.0rc0
+cb2b2242428df5be87693b8acaac71deafcff2c5 7.0rc1
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
index a232522d377076537ae60536fe868b1623e9515d_bWVyY3VyaWFsL2J1bmRsZTIucHk=..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_bWVyY3VyaWFsL2J1bmRsZTIucHk= 100644
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -939,7 +939,16 @@
             yield _pack(_fstreamparamsize, paramssize)
         # From there, payload might need to be decompressed
         self._fp = self._compengine.decompressorreader(self._fp)
-        emptycount = 0
+
+        # We usually wait for empty terminators: a bundle part terminator
+        # followed by a bundle terminator.
+        #
+        # Since the empty bundle has no parts,
+        # bundle part terminator never comes.
+        #
+        # So we boostrap this detection with am empty part, which the first
+        # piece of part will reset.
+        emptycount = 1
         while emptycount < 2:
             # so we can brainlessly loop
             assert _fpartheadersize == _fpayloadsize
diff --git a/mercurial/bundlecaches.py b/mercurial/bundlecaches.py
index a232522d377076537ae60536fe868b1623e9515d_bWVyY3VyaWFsL2J1bmRsZWNhY2hlcy5weQ==..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_bWVyY3VyaWFsL2J1bmRsZWNhY2hlcy5weQ== 100644
--- a/mercurial/bundlecaches.py
+++ b/mercurial/bundlecaches.py
@@ -102,8 +102,12 @@
 
     def as_spec(self):
         parts = [b"%s-%s" % (self.compression, self.version)]
-        for param in sorted(self._explicit_params.items()):
-            parts.append(b'%s=%s' % param)
+        for param, raw_value in sorted(self._explicit_params.items()):
+            if isinstance(raw_value, bool):
+                value = b"yes" if raw_value else b"no"
+            else:
+                value = raw_value
+            parts.append(b'%s=%s' % (param, value))
         return b';'.join(parts)
 
 
diff --git a/mercurial/configitems.toml b/mercurial/configitems.toml
index a232522d377076537ae60536fe868b1623e9515d_bWVyY3VyaWFsL2NvbmZpZ2l0ZW1zLnRvbWw=..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_bWVyY3VyaWFsL2NvbmZpZ2l0ZW1zLnRvbWw= 100644
--- a/mercurial/configitems.toml
+++ b/mercurial/configitems.toml
@@ -2988,8 +2988,8 @@
 When set to 0, the default, the value used depends on the value of the
 `usage.resources.cpu` configuration:
 - low:    1 writer
-- medium: 2 writers (default)
-- high:   4 writers
+- medium: 4 writers (default)
+- high:   8 writers
 """
 
 [[items]]
@@ -3003,8 +3003,8 @@
 
 When set to 0, the default value depends on the `usage.resources.memory` value.
 - low:    100 MB,
-- medium:   1 GB,
-- high:   unrestricted memory usage.
+- medium: 500 MB,
+- high:     2 GB
 """
 
 
diff --git a/mercurial/vfs.py b/mercurial/vfs.py
index a232522d377076537ae60536fe868b1623e9515d_bWVyY3VyaWFsL3Zmcy5weQ==..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_bWVyY3VyaWFsL3Zmcy5weQ== 100644
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -476,5 +476,6 @@
             <path> is the real file system path content should be written to,
             <mode> is the file mode that need to be set if any.
         """
-        self._auditpath(path, b'wb')
+        real_path = self.join(path)
+        self._auditpath(real_path, b'wb')
         self.register_file(path)
@@ -480,5 +481,4 @@
         self.register_file(path)
-        real_path = self.join(path)
         dirname, basename = util.split(real_path)
         if dirname not in known_directories:
             util.makedirs(dirname, self.createmode, True)
diff --git a/relnotes/7.0 b/relnotes/7.0
index a232522d377076537ae60536fe868b1623e9515d_cmVsbm90ZXMvNy4w..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_cmVsbm90ZXMvNy4w 100644
--- a/relnotes/7.0
+++ b/relnotes/7.0
@@ -53,6 +53,9 @@
 - rhg: set the expected dirstate permissions (0o666 minus umask) (a48c688d3e80)
 - rhg: fix matcher issue (136e74c2bf8f)
 - rhg files correctly implements `--rev` (it instead provided `--revision`)
+- clone-bundles: fix background spawning of automatic generation
+- bundle-spec: properly format boolean parameter (issue6960)
+- bundle2: fix a bug where _forwardchunks doesn't work with empty bundles
 
 == Rust ==
 
diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs
index a232522d377076537ae60536fe868b1623e9515d_cnVzdC9oZy1jb3JlL3NyYy92ZnMucnM=..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_cnVzdC9oZy1jb3JlL3NyYy92ZnMucnM= 100644
--- a/rust/hg-core/src/vfs.rs
+++ b/rust/hg-core/src/vfs.rs
@@ -929,7 +929,7 @@
 /// a local clone, and we don't want to modify the original repo.
 fn copy_in_place_if_hardlink(path: &Path) -> Result<(), HgError> {
     let metadata = path.metadata().when_writing_file(path)?;
-    if metadata.nlink() > 0 {
+    if metadata.nlink() > 1 {
         // If it's hardlinked, copy it and rename it back before changing it.
         let tmpdir = path.parent().expect("file at root");
         let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
diff --git a/tests/drawdag.py b/tests/drawdag.py
index a232522d377076537ae60536fe868b1623e9515d_dGVzdHMvZHJhd2RhZy5weQ==..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_dGVzdHMvZHJhd2RhZy5weQ== 100644
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -79,6 +79,20 @@
       # replace: A -> B -> C -> D  # chained 1 to 1 replacements
       # split: A -> B, C           # 1 to many
       # prune: A, B, C             # many to nothing
+
+Special comment can also be used to control file content for some revision.
+
+The example below create two extra files in A, update on in B and delete the
+other on in C (using a special content to mark the deletion).
+
+In all case the file matching the node name is created for all non-merge
+commit.
+
+    C # A/file/path=content in A
+    | # A/other/file/path=content for another file
+    B # B/file/path=updated content in B
+    | # C/other/file/path=<deleted>
+    A
 """
 
 import collections
@@ -300,7 +314,10 @@
             self._parents.append(repo[repo.nullid])
 
     def filectx(self, key):
-        return simplefilectx(key, self._added[key])
+        content = self._added.get(key)
+        if content is not None:
+            return simplefilectx(key, content)
+        return None
 
     def commit(self):
         return self._repo.commitctx(self)
@@ -381,6 +398,6 @@
     # parse comments to get extra file content instructions
     files = collections.defaultdict(dict)  # {(name, path): content}
     comments = list(_getcomments(text))
-    filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
+    filere = re.compile(br'^(\w+)/([-_\w/"]+)\s*=\s*(.*)$', re.M)
     for name, path, content in filere.findall(b'\n'.join(comments)):
         content = content.replace(br'\n', b'\n').replace(br'\1', b'\1')
@@ -385,6 +402,9 @@
     for name, path, content in filere.findall(b'\n'.join(comments)):
         content = content.replace(br'\n', b'\n').replace(br'\1', b'\1')
-        files[name][path] = content
+        if content == b"<deleted>":
+            files[name][path] = None
+        else:
+            files[name][path] = content
 
     committed = {None: repo.nullid}  # {name: node}
 
diff --git a/tests/test-bundle2.py b/tests/test-bundle2.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_dGVzdHMvdGVzdC1idW5kbGUyLnB5
--- /dev/null
+++ b/tests/test-bundle2.py
@@ -0,0 +1,33 @@
+import unittest
+from mercurial import bundle2, ui as uimod
+
+bundle20 = bundle2.bundle20
+unbundle20 = bundle2.unbundle20
+
+ui = uimod.ui.load()
+
+
+class Bundle2tests(unittest.TestCase):
+    def test_nonempty_bundle_forwardchunks(self):
+        bundler = bundle20(ui)
+        bundler.newpart(
+            b'cache:rev-branch-cache', data=b'some-data', mandatory=False
+        )
+        data = b''.join(list(bundler.getchunks()))
+        unbundle = unbundle20(ui, __import__("io").BytesIO(data[4:]))
+        forwarded_data = b''.join(list(unbundle._forwardchunks()))
+        self.assertEqual(data, forwarded_data)
+
+    def test_empty_bundle_forwardchunks(self):
+        bundler = bundle20(ui)
+        data = b''.join(list(bundler.getchunks()))
+        self.assertEqual(data, b'HG20\0\0\0\0\0\0\0\0')
+        unbundle = unbundle20(ui, __import__("io").BytesIO(data[4:]))
+        forwarded_data = b''.join(list(unbundle._forwardchunks()))
+        self.assertEqual(data, forwarded_data)
+
+
+if __name__ == '__main__':
+    import silenttestrunner
+
+    silenttestrunner.main(__name__)
diff --git a/tests/test-clonebundles-autogen.t b/tests/test-clonebundles-autogen.t
index a232522d377076537ae60536fe868b1623e9515d_dGVzdHMvdGVzdC1jbG9uZWJ1bmRsZXMtYXV0b2dlbi50..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_dGVzdHMvdGVzdC1jbG9uZWJ1bmRsZXMtYXV0b2dlbi50 100644
--- a/tests/test-clonebundles-autogen.t
+++ b/tests/test-clonebundles-autogen.t
@@ -446,6 +446,21 @@
   $ $RUNTESTDIR/testlib/wait-on-file 30 $v1_file
   $ $RUNTESTDIR/testlib/wait-on-file 30 $v2_file
 
+all file created, but some cleanup might still be in progress, we wait 30 second for them
+
+# note: we should adjust that timing according to the test timeout, but that is more a
+#       change for the default branch than the stable branch
+
+  $ for x in `$RUNTESTDIR/seq.py 30`; do
+  >      if grep --invert-match DONE ../server/.hg/clonebundles.auto-gen > /dev/null; then
+  >          # some task still running
+  >          sleep 1
+  >          continue
+  >      fi
+  >      # all task are done running
+  >      break
+  > done
+
 We should have bundle now
 
   $ cat ../server/.hg/clonebundles.manifest
@@ -575,6 +590,7 @@
   full-bzip2-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
   full-bzip2-v2-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
   $ ls -1 ../final-upload
+  $ cd ..
 
 Check the url is correct
 ------------------------
@@ -589,3 +605,44 @@
   searching for changes
   no changes found
   15 local changesets published
+
+Test spec with boolean component (issue6960)
+============================================
+
+  $ cat >> ./server/.hg/hgrc << EOF
+  > [clone-bundles]
+  > auto-generate.formats = gzip-v2;obsolescence=yes,gzip-v3;obsolescence=no,gzip-v3;obsolescence=yes;obsolescence-mandatory=no
+  > EOF
+
+  $ rm ./server/.hg/clonebundles.manifest
+  $ hg -R ./server/ admin::clone-bundles-refresh
+  clone-bundles: deleting inline bundle full-bzip2-v1-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
+  clone-bundles: deleting inline bundle full-bzip2-v2-13_revs-8a81f9be54ea_tip-*_acbr.hg (glob)
+  clone-bundles: starting bundle generation: gzip-v2;obsolescence=yes
+  15 changesets found
+  clone-bundles: starting bundle generation: gzip-v3;obsolescence=no
+  15 changesets found
+  clone-bundles: starting bundle generation: gzip-v3;obsolescence=yes;obsolescence-mandatory=no
+  15 changesets found
+  $ cat ./server/.hg/clonebundles.manifest
+  peer-bundle-cache://full-bzip2-v1-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=bzip2-v1 (glob)
+  peer-bundle-cache://full-bzip2-v2-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=bzip2-v2 (glob)
+  peer-bundle-cache://full-gzip-v2;obsolescence=yes-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=gzip-v2;obsolescence=yes (glob)
+  peer-bundle-cache://full-gzip-v3;obsolescence=no-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=gzip-v3;obsolescence=no (glob)
+  peer-bundle-cache://full-gzip-v3;obsolescence=yes;obsolescence-mandatory=no-15_revs-17615b3984c2_tip-*_acbr.hg BUNDLESPEC=gzip-v3;obsolescence=yes;obsolescence-mandatory=no (glob)
+
+
+Check the manifest is correct
+-----------------------------
+
+  $ hg clone -U ssh://user@dummy/server ssh-inline-more-param-clone
+  applying clone bundle from peer-bundle-cache://full-bzip2-v1-15_revs-17615b3984c2_tip-*_acbr.hg (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 15 changesets with 15 changes to 15 files
+  finished applying clone bundle
+  searching for changes
+  no changes found
+  15 local changesets published
+
diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
index a232522d377076537ae60536fe868b1623e9515d_dGVzdHMvdGVzdC1kcmF3ZGFnLnQ=..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_dGVzdHMvdGVzdC1kcmF3ZGFnLnQ= 100644
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -242,8 +242,12 @@
   > |\      # B/dir2/b = 34
   > A B     # C/dir1/c = 5
   >         # C/dir2/c = 6
+  >         # A/to-be"deleted" = I am doomed.
+  >         # B/to-be-deleted_too = The end is near.
+  >         # C/to-be"deleted"=<deleted>
+  >         # C/to-be-deleted_too = <deleted>
   >         # C/A = a
   >         # C/B = b
   > EOS
 
   $ hg log -G -T '{desc} {files}'
@@ -245,7 +249,7 @@
   >         # C/A = a
   >         # C/B = b
   > EOS
 
   $ hg log -G -T '{desc} {files}'
-  o    C A B dir1/c dir2/c
+  o    C A B dir1/c dir2/c to-be"deleted" to-be-deleted_too
   |\
@@ -251,3 +255,3 @@
   |\
-  | o  B B dir2/b
+  | o  B B dir2/b to-be-deleted_too
   |
@@ -253,3 +257,3 @@
   |
-  o  A A dir1/a
+  o  A A dir1/a to-be"deleted"
   
@@ -255,4 +259,33 @@
   
+  $ for r in `hg log -T '{rev} ' --rev "sort(all())"`; do
+  >     hg log --rev $r -T "### {tags} ###\n";
+  >     hg diff --change $r --stat;
+  > done
+  ### A ###
+   A              |  1 +
+   dir1/a         |  2 ++
+   to-be"deleted" |  1 +
+   3 files changed, 4 insertions(+), 0 deletions(-)
+  ### B ###
+   B                 |  1 +
+   dir2/b            |  1 +
+   to-be-deleted_too |  1 +
+   3 files changed, 3 insertions(+), 0 deletions(-)
+  ### C tip ###
+   A              |  2 +-
+   B              |  1 +
+   dir1/c         |  1 +
+   dir2/b         |  1 +
+   dir2/c         |  1 +
+   to-be"deleted" |  1 -
+   6 files changed, 5 insertions(+), 2 deletions(-)
+  $ hg files -r C
+  A
+  B
+  dir1/a
+  dir1/c
+  dir2/b
+  dir2/c
   $ for f in `hg files -r C`; do
   >   echo FILE "$f"
   >   hg cat -r C "$f"
diff --git a/tests/test-stream-bundle-v2.t b/tests/test-stream-bundle-v2.t
index a232522d377076537ae60536fe868b1623e9515d_dGVzdHMvdGVzdC1zdHJlYW0tYnVuZGxlLXYyLnQ=..1cc4a52d9256dd09816d4e9d6b58e1b49e06772c_dGVzdHMvdGVzdC1zdHJlYW0tYnVuZGxlLXYyLnQ= 100644
--- a/tests/test-stream-bundle-v2.t
+++ b/tests/test-stream-bundle-v2.t
@@ -36,6 +36,8 @@
   $ cp $HGRCPATH $TESTTMP/hgrc.orig
 
   $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > portablefilenames=abort
   > [experimental]
   > evolution.createmarkers=True
   > evolution.exchange=True
@@ -60,5 +62,5 @@
   $ hg debugdrawdag <<'EOF'
   > E
   > |
-  > D
+  > D  # D/ba"r=<deleted>
   > |
@@ -64,3 +66,3 @@
   > |
-  > C
+  > C  # C/ba"r=faz
   > |
@@ -66,5 +68,5 @@
   > |
-  > B
+  > B  # B/blu=fuz
   > |
   > A
   > EOF
@@ -72,9 +74,9 @@
   $ hg bundle -a --type="none-v2;stream=$stream_version" bundle.hg
   $ hg debugbundle bundle.hg
   Stream params: {}
-  stream2 -- {bytecount: 1693, filecount: 12, requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 no-zstd !)
-  stream2 -- {bytecount: 1693, filecount: 12, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 zstd no-rust !)
-  stream2 -- {bytecount: 1819, filecount: 14, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 rust !)
+  stream2 -- {bytecount: 1908, filecount: 14, requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 no-zstd !)
+  stream2 -- {bytecount: 1911, filecount: 14, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 zstd no-rust !)
+  stream2 -- {bytecount: 2103, filecount: 16, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 rust !)
   stream3-exp -- {requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 no-zstd !)
   stream3-exp -- {requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 zstd no-rust !)
   stream3-exp -- {requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 rust !)
@@ -89,9 +91,9 @@
   $ hg bundle -a --type="none-$bundle_format" bundle.hg
   $ hg debugbundle bundle.hg
   Stream params: {}
-  stream2 -- {bytecount: 1693, filecount: 12, requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 no-zstd !)
-  stream2 -- {bytecount: 1693, filecount: 12, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 zstd no-rust !)
-  stream2 -- {bytecount: 1819, filecount: 14, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 rust !)
+  stream2 -- {bytecount: 1908, filecount: 14, requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 no-zstd !)
+  stream2 -- {bytecount: 1911, filecount: 14, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 zstd no-rust !)
+  stream2 -- {bytecount: 2103, filecount: 16, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v2 rust !)
   stream3-exp -- {requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 no-zstd !)
   stream3-exp -- {requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 zstd no-rust !)
   stream3-exp -- {requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (stream-v3 rust !)
@@ -133,8 +135,8 @@
   adding changesets
   adding manifests
   adding file changes
-  added 5 changesets with 5 changes to 5 files
-  new changesets 426bada5c675:9bc730a19041 (5 drafts)
+  added 5 changesets with 7 changes to 7 files
+  new changesets 426bada5c675:92165ab525bf (5 drafts)
 
 known requirements
 ------------------
@@ -193,8 +195,9 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "stream2" (params: 3 mandatory) supported
   applying stream bundle
-  12 files to transfer, 1.65 KB of data (no-rust !)
-  14 files to transfer, 1.78 KB of data (rust !)
+  14 files to transfer, 1.86 KB of data (no-rust no-zstd !)
+  14 files to transfer, 1.87 KB of data (no-rust zstd !)
+  16 files to transfer, 2.05 KB of data (rust !)
   starting 4 threads for background file closing (?)
   starting 4 threads for background file closing (?)
   adding [s] data/A.i (66 bytes)
@@ -202,4 +205,6 @@
   adding [s] data/C.i (66 bytes)
   adding [s] data/D.i (66 bytes)
   adding [s] data/E.i (66 bytes)
+  adding [s] data/ba"r.i (68 bytes)
+  adding [s] data/blu.i (68 bytes)
   adding [s] phaseroots (43 bytes)
@@ -205,3 +210,5 @@
   adding [s] phaseroots (43 bytes)
-  adding [s] 00manifest.i (584 bytes)
+  adding [s] 00manifest.i (649 bytes) (no-zstd !)
+  adding [s] 00manifest.i (652 bytes) (zstd no-rust !)
+  adding [s] 00manifest.i (654 bytes) (zstd rust !)
   adding [s] 00changelog.n (62 bytes) (rust !)
@@ -207,7 +214,7 @@
   adding [s] 00changelog.n (62 bytes) (rust !)
-  adding [s] 00changelog-b875dfc5.nd (64 bytes) (rust !)
-  adding [s] 00changelog.d (275 bytes)
+  adding [s] 00changelog-b875dfc5.nd (128 bytes) (rust !)
+  adding [s] 00changelog.d (289 bytes)
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
@@ -210,11 +217,13 @@
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
-  bundle2-input-part: total payload size 1857 (no-rust !)
-  bundle2-input-part: total payload size 2025 (rust !)
-  stream-cloned 12 files / 1.65 KB in * seconds (* */sec) (glob) (no-rust !)
-  stream-cloned 14 files / 1.78 KB in * seconds (* */sec) (glob) (rust !)
+  bundle2-input-part: total payload size 2099 (no-rust no-zstd !)
+  bundle2-input-part: total payload size 2102 (no-rust zstd !)
+  bundle2-input-part: total payload size 2337 (rust !)
+  stream-cloned 14 files / 1.86 KB in * seconds (* */sec) (glob) (no-rust no-zstd !)
+  stream-cloned 14 files / 1.87 KB in * seconds (* */sec) (glob) (no-rust zstd !)
+  stream-cloned 16 files / 2.05 KB in * seconds (* */sec) (glob) (rust !)
   bundle2-input-bundle: 1 parts total
   updating the branch cache
   finished applying clone bundle
@@ -233,7 +242,7 @@
   updating to branch default
   resolving manifests (no-rust !)
    branchmerge: False, force: False, partial: False (no-rust !)
-   ancestor: 000000000000, local: 000000000000+, remote: 9bc730a19041 (no-rust !)
+   ancestor: 000000000000, local: 000000000000+, remote: 92165ab525bf (no-rust !)
    A: remote created -> g (no-rust !)
   getting A (no-rust !)
    B: remote created -> g (no-rust !)
@@ -244,7 +253,9 @@
   getting D (no-rust !)
    E: remote created -> g (no-rust !)
   getting E (no-rust !)
-  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+   blu: remote created -> g (no-rust !)
+  getting blu (no-rust !)
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating the branch cache
   (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
 
@@ -261,8 +272,9 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "stream2" (params: 3 mandatory) supported
   applying stream bundle
-  12 files to transfer, 1.65 KB of data (no-rust !)
-  14 files to transfer, 1.78 KB of data (rust !)
+  14 files to transfer, 1.86 KB of data (no-rust no-zstd !)
+  14 files to transfer, 1.87 KB of data (no-rust zstd !)
+  16 files to transfer, 2.05 KB of data (rust !)
   starting 4 threads for background file closing (?)
   starting 4 threads for background file closing (?)
   adding [s] data/A.i (66 bytes)
@@ -270,4 +282,6 @@
   adding [s] data/C.i (66 bytes)
   adding [s] data/D.i (66 bytes)
   adding [s] data/E.i (66 bytes)
+  adding [s] data/ba"r.i (68 bytes)
+  adding [s] data/blu.i (68 bytes)
   adding [s] phaseroots (43 bytes)
@@ -273,3 +287,5 @@
   adding [s] phaseroots (43 bytes)
-  adding [s] 00manifest.i (584 bytes)
+  adding [s] 00manifest.i (649 bytes) (no-zstd !)
+  adding [s] 00manifest.i (652 bytes) (zstd no-rust !)
+  adding [s] 00manifest.i (654 bytes) (zstd rust !)
   adding [s] 00changelog.n (62 bytes) (rust !)
@@ -275,7 +291,7 @@
   adding [s] 00changelog.n (62 bytes) (rust !)
-  adding [s] 00changelog-b875dfc5.nd (64 bytes) (rust !)
-  adding [s] 00changelog.d (275 bytes)
+  adding [s] 00changelog-b875dfc5.nd (128 bytes) (rust !)
+  adding [s] 00changelog.d (289 bytes)
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
@@ -278,11 +294,13 @@
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
-  bundle2-input-part: total payload size 1857 (no-rust !)
-  bundle2-input-part: total payload size 2025 (rust !)
-  stream-cloned 12 files / 1.65 KB in * seconds (* */sec) (glob) (no-rust !)
-  stream-cloned 14 files / 1.78 KB in * seconds (* */sec) (glob) (rust !)
+  bundle2-input-part: total payload size 2099 (no-rust no-zstd !)
+  bundle2-input-part: total payload size 2102 (no-rust zstd !)
+  bundle2-input-part: total payload size 2337 (rust !)
+  stream-cloned 14 files / 1.86 KB in * seconds (* */sec) (glob) (no-rust no-zstd !)
+  stream-cloned 14 files / 1.87 KB in * seconds (* */sec) (glob) (no-rust zstd !)
+  stream-cloned 16 files / 2.05 KB in * seconds (* */sec) (glob) (rust !)
   bundle2-input-bundle: 1 parts total
   updating the branch cache
   finished applying clone bundle
@@ -301,7 +319,7 @@
   updating to branch default
   resolving manifests (no-rust !)
    branchmerge: False, force: False, partial: False (no-rust !)
-   ancestor: 000000000000, local: 000000000000+, remote: 9bc730a19041 (no-rust !)
+   ancestor: 000000000000, local: 000000000000+, remote: 92165ab525bf (no-rust !)
    A: remote created -> g (no-rust !)
   getting A (no-rust !)
    B: remote created -> g (no-rust !)
@@ -312,7 +330,9 @@
   getting D (no-rust !)
    E: remote created -> g (no-rust !)
   getting E (no-rust !)
-  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+   blu: remote created -> g (no-rust !)
+  getting blu (no-rust !)
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating the branch cache
   (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
 
@@ -327,7 +347,7 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "stream3-exp" (params: 1 mandatory) supported
   applying stream bundle
-  11 entries to transfer
+  13 entries to transfer
   starting 4 threads for background file closing (?)
   starting 4 threads for background file closing (?)
   adding [s] data/A.i (66 bytes)
@@ -335,4 +355,6 @@
   adding [s] data/C.i (66 bytes)
   adding [s] data/D.i (66 bytes)
   adding [s] data/E.i (66 bytes)
+  adding [s] data/ba"r.i (68 bytes)
+  adding [s] data/blu.i (68 bytes)
   adding [s] phaseroots (43 bytes)
@@ -338,3 +360,5 @@
   adding [s] phaseroots (43 bytes)
-  adding [s] 00manifest.i (584 bytes)
+  adding [s] 00manifest.i (649 bytes) (no-zstd !)
+  adding [s] 00manifest.i (652 bytes) (zstd no-rust !)
+  adding [s] 00manifest.i (654 bytes) (zstd rust !)
   adding [s] 00changelog.n (62 bytes) (rust !)
@@ -340,7 +364,7 @@
   adding [s] 00changelog.n (62 bytes) (rust !)
-  adding [s] 00changelog-b875dfc5.nd (64 bytes) (rust !)
-  adding [s] 00changelog.d (275 bytes)
+  adding [s] 00changelog-b875dfc5.nd (128 bytes) (rust !)
+  adding [s] 00changelog.d (289 bytes)
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
@@ -343,11 +367,13 @@
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
-  stream-cloned 12 files / 1.65 KB in * seconds (* */sec) (glob) (no-rust !)
-  bundle2-input-part: total payload size 1869 (no-rust !)
-  stream-cloned 14 files / 1.78 KB in * seconds (* */sec) (glob) (rust !)
-  bundle2-input-part: total payload size 2037 (rust !)
+  stream-cloned 14 files / 1.86 KB in * seconds (* */sec) (glob) (no-rust no-zstd !)
+  stream-cloned 14 files / 1.87 KB in * seconds (* */sec) (glob) (no-rust zstd !)
+  bundle2-input-part: total payload size 2113 (no-rust no-zstd !)
+  bundle2-input-part: total payload size 2116 (no-rust zstd !)
+  stream-cloned 16 files / 2.05 KB in * seconds (* */sec) (glob) (rust !)
+  bundle2-input-part: total payload size 2351 (rust !)
   bundle2-input-bundle: 1 parts total
   updating the branch cache
   finished applying clone bundle
@@ -366,7 +392,7 @@
   updating to branch default
   resolving manifests (no-rust !)
    branchmerge: False, force: False, partial: False (no-rust !)
-   ancestor: 000000000000, local: 000000000000+, remote: 9bc730a19041 (no-rust !)
+   ancestor: 000000000000, local: 000000000000+, remote: 92165ab525bf (no-rust !)
    A: remote created -> g (no-rust !)
   getting A (no-rust !)
    B: remote created -> g (no-rust !)
@@ -377,7 +403,9 @@
   getting D (no-rust !)
    E: remote created -> g (no-rust !)
   getting E (no-rust !)
-  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+   blu: remote created -> g (no-rust !)
+  getting blu (no-rust !)
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating the branch cache
   (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)
 
@@ -389,7 +417,7 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "stream3-exp" (params: 1 mandatory) supported
   applying stream bundle
-  11 entries to transfer
+  13 entries to transfer
   starting 4 threads for background file closing (?)
   starting 4 threads for background file closing (?)
   adding [s] data/A.i (66 bytes)
@@ -397,4 +425,6 @@
   adding [s] data/C.i (66 bytes)
   adding [s] data/D.i (66 bytes)
   adding [s] data/E.i (66 bytes)
+  adding [s] data/ba"r.i (68 bytes)
+  adding [s] data/blu.i (68 bytes)
   adding [s] phaseroots (43 bytes)
@@ -400,3 +430,5 @@
   adding [s] phaseroots (43 bytes)
-  adding [s] 00manifest.i (584 bytes)
+  adding [s] 00manifest.i (649 bytes) (no-zstd !)
+  adding [s] 00manifest.i (652 bytes) (zstd no-rust !)
+  adding [s] 00manifest.i (654 bytes) (zstd rust !)
   adding [s] 00changelog.n (62 bytes) (rust !)
@@ -402,7 +434,7 @@
   adding [s] 00changelog.n (62 bytes) (rust !)
-  adding [s] 00changelog-b875dfc5.nd (64 bytes) (rust !)
-  adding [s] 00changelog.d (275 bytes)
+  adding [s] 00changelog-b875dfc5.nd (128 bytes) (rust !)
+  adding [s] 00changelog.d (289 bytes)
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
@@ -405,11 +437,13 @@
   adding [s] 00changelog.i (320 bytes)
   adding [c] branch2-served (94 bytes)
   adding [c] rbc-names-v2 (7 bytes)
   adding [c] rbc-revs-v2 (40 bytes)
-  stream-cloned 12 files / 1.65 KB in * seconds (* */sec) (glob) (no-rust !)
-  bundle2-input-part: total payload size 1869 (no-rust !)
-  stream-cloned 14 files / 1.78 KB in * seconds (* */sec) (glob) (rust !)
-  bundle2-input-part: total payload size 2037 (rust !)
+  stream-cloned 14 files / 1.86 KB in * seconds (* */sec) (glob) (no-rust no-zstd !)
+  stream-cloned 14 files / 1.87 KB in * seconds (* */sec) (glob) (no-rust zstd !)
+  bundle2-input-part: total payload size 2113 (no-rust no-zstd !)
+  bundle2-input-part: total payload size 2116 (no-rust zstd !)
+  stream-cloned 16 files / 2.05 KB in * seconds (* */sec) (glob) (rust !)
+  bundle2-input-part: total payload size 2351 (rust !)
   bundle2-input-bundle: 1 parts total
   updating the branch cache
   finished applying clone bundle
@@ -428,7 +462,7 @@
   updating to branch default
   resolving manifests (no-rust !)
    branchmerge: False, force: False, partial: False (no-rust !)
-   ancestor: 000000000000, local: 000000000000+, remote: 9bc730a19041 (no-rust !)
+   ancestor: 000000000000, local: 000000000000+, remote: 92165ab525bf (no-rust !)
    A: remote created -> g (no-rust !)
   getting A (no-rust !)
    B: remote created -> g (no-rust !)
@@ -439,7 +473,9 @@
   getting D (no-rust !)
    E: remote created -> g (no-rust !)
   getting E (no-rust !)
-  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+   blu: remote created -> g (no-rust !)
+  getting blu (no-rust !)
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating the branch cache
   (sent 4 HTTP requests and * bytes; received * bytes in responses) (glob)