bundlerepo: apply phase data stored in the bundle instead of assuming `draft`
authorMatt Harbison <matt_harbison@yahoo.com>, Pierre-Yves David <pierre-yves.david@octobus.net>
Thu, 23 Feb 2023 23:05:51 +0100
changeset 50200 197204dba8a2
parent 50199 21f876895dfe
child 50201 149f09ffef46
bundlerepo: apply phase data stored in the bundle instead of assuming `draft` The phase information contained in the changegroup part and the explicit `phase-heads` part are now taken in account. Initial changes and test by Matt Harbison, code rework by Pierre-Yves David.
mercurial/bundlerepo.py
tests/test-bundle-phases.t
--- a/mercurial/bundlerepo.py	Thu Feb 23 19:07:58 2023 +0100
+++ b/mercurial/bundlerepo.py	Thu Feb 23 23:05:51 2023 +0100
@@ -302,7 +302,9 @@
 
             cgpart = None
             for part in bundle.iterparts(seekable=True):
-                if part.type == b'changegroup':
+                if part.type == b'phase-heads':
+                    self._handle_bundle2_phase_part(bundle, part)
+                elif part.type == b'changegroup':
                     if cgpart:
                         raise NotImplementedError(
                             b"can't process multiple changegroups"
@@ -346,6 +348,17 @@
     def _handle_bundle2_cg_part(self, bundle, part):
         assert part.type == b'changegroup'
         cgstream = part
+        targetphase = part.params.get(b'targetphase')
+        try:
+            targetphase = int(targetphase)
+        except TypeError:
+            pass
+        if targetphase is None:
+            targetphase = phases.draft
+        if targetphase not in phases.allphases:
+            m = _(b'unsupported targetphase: %d')
+            m %= targetphase
+            raise error.Abort(m)
         version = part.params.get(b'version', b'01')
         legalcgvers = changegroup.supportedincomingversions(self)
         if version not in legalcgvers:
@@ -360,10 +373,17 @@
         phases.retractboundary(
             self,
             None,
-            phases.draft,
+            targetphase,
             [ctx.node() for ctx in self[self.firstnewrev :]],
         )
 
+    def _handle_bundle2_phase_part(self, bundle, part):
+        assert part.type == b'phase-heads'
+
+        unfi = self.unfiltered()
+        headsbyphase = phases.binarydecode(part)
+        phases.updatephases(unfi, lambda: None, headsbyphase)
+
     def _writetempbundle(self, readfn, suffix, header=b''):
         """Write a temporary file to disk"""
         fdtemp, temp = self.vfs.mkstemp(prefix=b"hg-bundle-", suffix=suffix)
--- a/tests/test-bundle-phases.t	Thu Feb 23 19:07:58 2023 +0100
+++ b/tests/test-bundle-phases.t	Thu Feb 23 23:05:51 2023 +0100
@@ -33,7 +33,6 @@
   |
   o  A public
   
-Phases are restored when unbundling
   $ hg bundle --base B -r E bundle
   3 changesets found
   $ hg debugbundle bundle
@@ -46,6 +45,57 @@
   phase-heads -- {} (mandatory: True)
       26805aba1e600a82e93661149f2313866a221a7b draft
   $ hg strip --no-backup C
+
+Phases show on incoming, and are also restored when pulling.  Secret commits
+aren't incoming or pulled, following usual incoming/pull semantics.
+
+  $ hg log -R bundle -r 'bundle()^+bundle()' -G -T '{desc} {phase}\n'
+  o  E secret
+  |
+  o  D secret
+  |
+  o  C draft
+  |
+  o  B draft
+  |
+  ~
+
+  $ hg incoming bundle -G -T '{desc} {phase}\n'
+  comparing with bundle
+  searching for changes
+  o  C draft
+  
+  $ hg pull bundle
+  pulling from bundle
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets 26805aba1e60 (1 drafts)
+  (run 'hg update' to get a working copy)
+  $ hg log -G -T '{desc} {phase}\n'
+  o  C draft
+  |
+  o  B draft
+  |
+  o  A public
+  
+  $ hg log -R bundle -r 'bundle()^+bundle()' -G -T '{desc} {phase}\n'
+  o  E secret
+  |
+  o  D secret
+  |
+  o  C draft
+  |
+  o  B draft
+  |
+  ~
+
+  $ hg rollback --config ui.rollback=1
+  repository tip rolled back to revision 1 (undo pull)
+
+Phases are restored when unbundling
   $ hg unbundle -q bundle
   $ rm bundle
   $ hg log -G -T '{desc} {phase}\n'
@@ -64,7 +114,27 @@
   5 changesets found
   $ hg strip --no-backup A
   $ hg unbundle -q bundle
-  $ rm bundle
+  $ hg log -G -T '{desc} {phase}\n'
+  o  E secret
+  |
+  o  D secret
+  |
+  o  C draft
+  |
+  o  B draft
+  |
+  o  A public
+  
+  $ hg init empty
+  $ hg -R empty pull bundle
+  pulling from bundle
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  new changesets 426bada5c675:26805aba1e60 (2 drafts)
+  (run 'hg update' to get a working copy)
   $ hg log -G -T '{desc} {phase}\n'
   o  E secret
   |
@@ -76,8 +146,74 @@
   |
   o  A public
   
+
+Public repo commits take precedence over phases in the bundle
+  $ hg phase --public E
+  $ hg incoming bundle -G -T '{desc} {phase}\n'
+  comparing with bundle
+  searching for changes
+  no changes found
+  $ hg log -R bundle -r 'bundle()^+bundle()' -G -T '{desc} {phase}\n'
+  o  E public
+  |
+  o  D public
+  |
+  o  C public
+  |
+  o  B public
+  |
+  o  A public
+  
+  $ hg pull bundle
+  pulling from bundle
+  searching for changes
+  no changes found
+  $ hg log -G -T '{desc} {phase}\n'
+  o  E public
+  |
+  o  D public
+  |
+  o  C public
+  |
+  o  B public
+  |
+  o  A public
+  
+  $ rm bundle
+
+A bundle with public phases that are not public in the repo will show as public
+with `hg log`, but will remain not public in the plain repo.
+
+  $ hg bundle --base B -r E bundle
+  3 changesets found
+  $ hg phase --force --draft -r C
+
+  $ hg log -R bundle -G -T '{desc} {phase}\n'
+  o  E public
+  |
+  o  D public
+  |
+  o  C public
+  |
+  o  B public
+  |
+  o  A public
+  
+  $ hg log -G -T '{desc} {phase}\n'
+  o  E draft
+  |
+  o  D draft
+  |
+  o  C draft
+  |
+  o  B public
+  |
+  o  A public
+  
+  $ hg phase --public -r E
+  $ rm bundle
+
 Completely public history can be restored
-  $ hg phase --public E
   $ hg bundle -a bundle
   5 changesets found
   $ hg strip --no-backup A