phases: fix phase synchronization on push
authorPierre-Yves David <pierre-yves.david@ens-lyon.org>
Wed, 04 Jan 2012 01:12:31 +0100
changeset 15820 2673006f7989
parent 15819 33ca11b010e2
child 15821 e3ee8bf5d0cc
phases: fix phase synchronization on push The bugs seemed to show up when element not in future common changeset should hold new hold phase data. The whole phase push machinery was rewritten in the process.
mercurial/localrepo.py
tests/test-phases.t
tests/test-push-http.t
--- a/mercurial/localrepo.py	Fri Jan 06 10:04:20 2012 +0100
+++ b/mercurial/localrepo.py	Wed Jan 04 01:12:31 2012 +0100
@@ -1632,21 +1632,60 @@
                     # XXX If push failed we should use strict common and not
                     # future to avoir pushing phase data on unknown changeset.
                     # This is to done later.
-                    futctx = [self[n] for n in fut if n != nullid]
-                    for phase in phases.trackedphases[::-1]:
-                        prevphase = phase -1
-                        # get all candidate for head in previous phase
-                        inprev = [ctx for ctx in futctx
-                                      if ctx.phase() == prevphase]
-                        for newremotehead in  self.set('heads(%ld & (%ln::))',
-                                              inprev, rroots[phase]):
-                            r = remote.pushkey('phases',
-                                               newremotehead.hex(),
-                                               str(phase), str(prevphase))
-                            if not r:
-                                self.ui.warn(_('updating phase of %s'
-                                               'to %s failed!\n')
-                                                % (newremotehead, prevphase))
+
+                    # element we want to push
+                    topush = []
+
+                    # store details of known remote phase of several revision
+                    # /!\ set of index I holds rev where: I <= rev.phase()
+                    # /!\ public phase (index 0) is ignored
+                    remdetails = [set() for i in xrange(len(phases.allphases))]
+                    _revs = set()
+                    for relremphase in phases.trackedphases[::-1]:
+                        # we iterate backward because the list alway grows
+                        # when filled in this direction.
+                        _revs.update(self.revs('%ln::%ln',
+                                               rroots[relremphase], fut))
+                        remdetails[relremphase].update(_revs)
+
+                    for phase in phases.allphases[:-1]:
+                        # We don't need the last phase as we will never want to
+                        # move anything to it while moving phase backward.
+
+                        # Get the list of all revs on remote which are in a
+                        # phase higher than currently processed phase.
+                        relremrev = remdetails[phase + 1]
+
+                        if not relremrev:
+                            # no candidate to remote push anymore
+                            # break before any expensive revset
+                            break
+
+                        #dynamical inject appropriate phase symbol
+                        phasename = phases.phasenames[phase]
+                        odrevset = 'heads(%%ld and %s())' % phasename
+                        outdated =  self.set(odrevset, relremrev)
+                        for od in outdated:
+                            candstart = len(remdetails) - 1
+                            candstop = phase + 1
+                            candidateold = xrange(candstart, candstop, -1)
+                            for oldphase in candidateold:
+                                if od.rev() in remdetails[oldphase]:
+                                    break
+                            else: # last one: no need to search
+                                oldphase = phase + 1
+                            topush.append((oldphase, phase, od))
+
+                    # push every needed data
+                    for oldphase, newphase, newremotehead in topush:
+                        r = remote.pushkey('phases',
+                                           newremotehead.hex(),
+                                           str(oldphase), str(newphase))
+                        if not r:
+                            self.ui.warn(_('updating phase of %s '
+                                           'to %s from %s failed!\n')
+                                            % (newremotehead, newphase,
+                                               oldphase))
             finally:
                 locallock.release()
         finally:
--- a/tests/test-phases.t	Fri Jan 06 10:04:20 2012 +0100
+++ b/tests/test-phases.t	Wed Jan 04 01:12:31 2012 +0100
@@ -91,6 +91,10 @@
 Test secret changeset are not pushed
 
   $ hg init ../push-dest
+  $ cat > ../push-dest/.hg/hgrc << EOF
+  > [phases]
+  > publish=False
+  > EOF
   $ hg push ../push-dest -f # force because we push multiple heads
   pushing to ../push-dest
   searching for changes
@@ -100,18 +104,18 @@
   added 5 changesets with 5 changes to 5 files (+1 heads)
   $ hglog
   7 2 merge B' and E
-  6 0 B'
+  6 1 B'
   5 2 H
   4 2 E
-  3 0 D
-  2 0 C
+  3 1 D
+  2 1 C
   1 0 B
   0 0 A
   $ cd ../push-dest
   $ hglog
-  4 0 B'
-  3 0 D
-  2 0 C
+  4 1 B'
+  3 1 D
+  2 1 C
   1 0 B
   0 0 A
   $ cd ..
@@ -142,10 +146,10 @@
   $ hglog -r 'public()'
   0 0 A
   1 0 B
-  2 0 C
-  3 0 D
-  6 0 B'
   $ hglog -r 'draft()'
+  2 1 C
+  3 1 D
+  6 1 B'
   $ hglog -r 'secret()'
   4 2 E
   5 2 H
--- a/tests/test-push-http.t	Fri Jan 06 10:04:20 2012 +0100
+++ b/tests/test-push-http.t	Wed Jan 04 01:12:31 2012 +0100
@@ -29,7 +29,7 @@
   searching for changes
   remote: ssl required
   remote: ssl required
-  updating phase of ba677d0156c1to 0 failed!
+  updating phase of ba677d0156c1 to 0 from 1 failed!
   % serve errors
 
 expect authorization error