changegroup: fix pulling to treemanifest repo from flat repo (issue5066) stable
authorMartin von Zweigbergk <martinvonz@google.com>
Wed, 27 Jan 2016 09:07:28 -0800
branchstable
changeset 29902 88609cfa37455815e4d6acd45e2a8893720c065b
parent 29901 c5ffbd4c033ba82942e51d03e05cf7df2de69d2f
child 29903 9960b6369e7f500211e44febda9c16bf66fde06a
child 29910 288f5b083e09188bc245218dbf83f8d090609b9a
push id175
push usergszorc@mozilla.com
push dateWed, 10 Feb 2016 01:24:28 +0000
changegroup: fix pulling to treemanifest repo from flat repo (issue5066) In c0f11347b107 (changegroup: don't support versions 01 and 02 with treemanifests, 2016-01-19), I stopped supporting use of cg1 and cg2 with treemanifest repos. What I had not considered was that it's perfectly safe to pull *to* a treemanifest repo using any changegroup version. As reported in issue5066, I therefore broke pull from old repos into a treemanifest repo. It was not covered by the test case, because that pulled from a local repo while enabling treemanifests, which enabled treemanifests on the source repo as well. After switching to pulling via HTTP, it breaks. Fix by splitting up changegroup.supportedversions() into supportedincomingversions() and supportedoutgoingversions().
mercurial/bundle2.py
mercurial/bundlerepo.py
mercurial/changegroup.py
mercurial/exchange.py
tests/test-treemanifest.t
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1237,17 +1237,18 @@ capabilities = {'HG20': (),
                }
 
 def getrepocaps(repo, allowpushback=False):
     """return the bundle2 capabilities for a given repo
 
     Exists to allow extensions (like evolution) to mutate the capabilities.
     """
     caps = capabilities.copy()
-    caps['changegroup'] = tuple(sorted(changegroup.supportedversions(repo)))
+    caps['changegroup'] = tuple(sorted(
+        changegroup.supportedincomingversions(repo)))
     if obsolete.isenabled(repo, obsolete.exchangeopt):
         supportedformat = tuple('V%i' % v for v in obsolete.formats)
         caps['obsmarkers'] = supportedformat
     if allowpushback:
         caps['pushback'] = ()
     return caps
 
 def bundle2caps(remote):
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -277,17 +277,17 @@ class bundlerepository(localrepo.localre
             cgstream = None
             for part in self.bundle.iterparts():
                 if part.type == 'changegroup':
                     if cgstream is not None:
                         raise NotImplementedError("can't process "
                                                   "multiple changegroups")
                     cgstream = part
                     version = part.params.get('version', '01')
-                    if version not in changegroup.supportedversions(self):
+                    if version not in changegroup.allsupportedversions(ui):
                         msg = _('Unsupported changegroup version: %s')
                         raise error.Abort(msg % version)
                     if self.bundle.compressed():
                         cgstream = _writetempbundle(part.read,
                                                     ".cg%sun" % version)
 
             if cgstream is None:
                 raise error.Abort('No changegroups found')
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -944,43 +944,57 @@ class cg3packer(cg2packer):
 
 _packermap = {'01': (cg1packer, cg1unpacker),
              # cg2 adds support for exchanging generaldelta
              '02': (cg2packer, cg2unpacker),
              # cg3 adds support for exchanging revlog flags and treemanifests
              '03': (cg3packer, cg3unpacker),
 }
 
-def supportedversions(repo):
+def allsupportedversions(ui):
     versions = set(_packermap.keys())
-    if ('treemanifest' in repo.requirements or
-        repo.ui.configbool('experimental', 'treemanifest')):
+    versions.discard('03')
+    if (ui.configbool('experimental', 'changegroup3') or
+        ui.configbool('experimental', 'treemanifest')):
+        versions.add('03')
+    return versions
+
+# Changegroup versions that can be applied to the repo
+def supportedincomingversions(repo):
+    versions = allsupportedversions(repo.ui)
+    if 'treemanifest' in repo.requirements:
+        versions.add('03')
+    return versions
+
+# Changegroup versions that can be created from the repo
+def supportedoutgoingversions(repo):
+    versions = allsupportedversions(repo.ui)
+    if 'treemanifest' in repo.requirements:
         # Versions 01 and 02 support only flat manifests and it's just too
         # expensive to convert between the flat manifest and tree manifest on
         # the fly. Since tree manifests are hashed differently, all of history
         # would have to be converted. Instead, we simply don't even pretend to
         # support versions 01 and 02.
         versions.discard('01')
         versions.discard('02')
-    elif not repo.ui.configbool('experimental', 'changegroup3'):
-        versions.discard('03')
+        versions.add('03')
     return versions
 
 def safeversion(repo):
     # Finds the smallest version that it's safe to assume clients of the repo
     # will support. For example, all hg versions that support generaldelta also
     # support changegroup 02.
-    versions = supportedversions(repo)
+    versions = supportedoutgoingversions(repo)
     if 'generaldelta' in repo.requirements:
         versions.discard('01')
     assert versions
     return min(versions)
 
 def getbundler(version, repo, bundlecaps=None):
-    assert version in supportedversions(repo)
+    assert version in supportedoutgoingversions(repo)
     return _packermap[version][0](repo, bundlecaps)
 
 def getunbundler(version, fh, alg):
     return _packermap[version][1](fh, alg)
 
 def _changegroupinfo(repo, nodes, source):
     if repo.ui.verbose or source == 'bundle':
         repo.ui.status(_("%d changesets found\n") % len(nodes))
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -702,17 +702,18 @@ def _pushb2ctx(pushop, bundler):
     b2caps = bundle2.bundle2caps(pushop.remote)
     version = None
     cgversions = b2caps.get('changegroup')
     if not cgversions:  # 3.1 and 3.2 ship with an empty value
         cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
                                                 pushop.outgoing)
     else:
         cgversions = [v for v in cgversions
-                      if v in changegroup.supportedversions(pushop.repo)]
+                      if v in changegroup.supportedoutgoingversions(
+                          pushop.repo)]
         if not cgversions:
             raise ValueError(_('no common changegroup version'))
         version = max(cgversions)
         cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
                                                 pushop.outgoing,
                                                 version=version)
     cgpart = bundler.newpart('changegroup', data=cg)
     if version is not None:
@@ -1557,17 +1558,17 @@ def _getbundlechangegrouppart(bundler, r
     cg = None
     if kwargs.get('cg', True):
         # build changegroup bundle here.
         version = None
         cgversions = b2caps.get('changegroup')
         getcgkwargs = {}
         if cgversions:  # 3.1 and 3.2 ship with an empty value
             cgversions = [v for v in cgversions
-                          if v in changegroup.supportedversions(repo)]
+                          if v in changegroup.supportedoutgoingversions(repo)]
             if not cgversions:
                 raise ValueError(_('no common changegroup version'))
             version = getcgkwargs['version'] = max(cgversions)
         outgoing = changegroup.computeoutgoing(repo, heads, common)
         cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
                                                 bundlecaps=bundlecaps,
                                                 **getcgkwargs)
 
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -1,11 +1,13 @@
   $ cat << EOF >> $HGRCPATH
   > [format]
   > usegeneraldelta=yes
+  > [ui]
+  > ssh=python "$TESTDIR/dummyssh"
   > EOF
 
 Set up repo
 
   $ hg --config experimental.treemanifest=True init repo
   $ cd repo
 
 Requirements get set on init
@@ -198,37 +200,50 @@ Create a few commits with flat manifest
   $ echo 2 > dir1/dir1/b
   $ hg ci -Aqm 'modify on branch 2'
 
   $ hg merge 1
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ hg ci -m 'merge of flat manifests to new flat manifest'
 
+  $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
 Create clone with tree manifests enabled
 
   $ cd ..
-  $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
-  requesting all changes
+  $ hg clone --config experimental.treemanifest=1 \
+  >   http://localhost:$HGPORT repo-mixed -r 1
   adding changesets
   adding manifests
   adding file changes
-  added 4 changesets with 17 changes to 11 files
+  added 2 changesets with 14 changes to 11 files
   updating to branch default
   11 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-mixed
   $ test -d .hg/store/meta
   [1]
   $ grep treemanifest .hg/requires
   treemanifest
 
+Should be possible to push updates from flat to tree manifest repo
+
+  $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
+  pushing to ssh://user@dummy/repo-mixed
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 2 changesets with 3 changes to 3 files
+
 Commit should store revlog per directory
 
   $ hg co 1
-  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 3 > a
   $ echo 3 > dir1/a
   $ echo 3 > dir1/dir1/a
   $ hg ci -m 'first tree'
   created new head
   $ find .hg/store/meta | sort
   .hg/store/meta
   .hg/store/meta/dir1
@@ -451,23 +466,23 @@ within that.
 
 Add some more changes to the deep repo
   $ echo narf >> b/bar/fruits.txt
   $ hg ci -m narf
   $ echo troz >> b/bar/orange/fly/gnat.py
   $ hg ci -m troz
 
 Test cloning a treemanifest repo over http.
-  $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
+  $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
   $ cat hg.pid >> $DAEMON_PIDS
   $ cd ..
 We can clone even with the knob turned off and we'll get a treemanifest repo.
   $ hg clone --config experimental.treemanifest=False \
   >   --config experimental.changegroup3=True \
-  >   http://localhost:$HGPORT deepclone
+  >   http://localhost:$HGPORT2 deepclone
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 3 changesets with 10 changes to 8 files
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
 No server errors.