Patchwork D3187: phase: add dry-run functionality

login
register
mail settings
Submitter phabricator
Date May 25, 2018, 8:34 p.m.
Message ID <d92f31d7f14c835b04ab827bdc3678bd@localhost.localdomain>
Download mbox | patch
Permalink /patch/31865/
State Not Applicable
Headers show

Comments

phabricator - May 25, 2018, 8:34 p.m.
khanchi97 updated this revision to Diff 8896.
khanchi97 edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D3187?vs=7877&id=8896

REVISION DETAIL
  https://phab.mercurial-scm.org/D3187

AFFECTED FILES
  mercurial/commands.py
  mercurial/phases.py
  tests/test-completion.t
  tests/test-phases.t

CHANGE DETAILS




To: khanchi97, pulkit, #hg-reviewers
Cc: av6, mercurial-devel

Patch

diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -826,3 +826,107 @@ 
   rollback completed
   abort: pretxnclose-phase.nopublish_D hook exited with status 1
   [255]
+
+Test dry-run functionality
+
+  $ hg init dryrunrepo
+  $ cd dryrunrepo
+  $ echo a > a
+  $ hg ci -qAm 0
+  test-debug-phase: new rev 0:  x -> 1
+  test-hook-close-phase: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac:   -> draft
+  $ echo b > b
+  $ hg ci -qAm 1
+  test-debug-phase: new rev 1:  x -> 1
+  test-hook-close-phase: 925d80f479bb026b0fb3deb27503780b13f74123:   -> draft
+  $ echo c > c
+  $ hg ci -qAm 2
+  test-debug-phase: new rev 2:  x -> 1
+  test-hook-close-phase: 0316ce92851d493393d2181900388caa05d256c3:   -> draft
+  $ echo d > d
+  $ hg ci -qAm 3
+  test-debug-phase: new rev 3:  x -> 1
+  test-hook-close-phase: 14b465a7e25bf201e963c055be0e780414cff648:   -> draft
+  $ echo e > e
+  $ hg ci -qAm 4
+  test-debug-phase: new rev 4:  x -> 1
+  test-hook-close-phase: b385d13d5ed4ceb2b67ced172470734a60187cd1:   -> draft
+  $ echo f > f
+  $ hg ci -qAm 5
+  test-debug-phase: new rev 5:  x -> 1
+  test-hook-close-phase: fdc0253c25cfd67fe42b7be81e3abc9f92bebbd5:   -> draft
+  $ hg up 3 -q
+  $ echo g > g
+  $ hg ci -qAm 6
+  test-debug-phase: new rev 6:  x -> 1
+  test-hook-close-phase: f19b7f89f44eee9ffe34ba58b4e4ee3b3cea1f34:   -> draft
+  $ echo h > h
+  $ hg ci -qAm 7
+  test-debug-phase: new rev 7:  x -> 1
+  test-hook-close-phase: 4ccc844d545402eb0f39cd294227cd38de3ece20:   -> draft
+
+
+  $ hg phase --public 1
+  test-debug-phase: move rev 0: 1 -> 0
+  test-debug-phase: move rev 1: 1 -> 0
+  test-hook-close-phase: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac:  draft -> public
+  test-hook-close-phase: 925d80f479bb026b0fb3deb27503780b13f74123:  draft -> public
+  $ hg phase --secret 4 --force
+  test-debug-phase: move rev 2: 1 -> 2
+  test-debug-phase: move rev 3: 1 -> 2
+  test-debug-phase: move rev 4: 1 -> 2
+  test-debug-phase: move rev 5: 1 -> 2
+  test-hook-close-phase: 0316ce92851d493393d2181900388caa05d256c3:  draft -> secret
+  test-hook-close-phase: 14b465a7e25bf201e963c055be0e780414cff648:  draft -> secret
+  test-hook-close-phase: b385d13d5ed4ceb2b67ced172470734a60187cd1:  draft -> secret
+  test-hook-close-phase: fdc0253c25cfd67fe42b7be81e3abc9f92bebbd5:  draft -> secret
+
+  $ hg log -G -T "{rev} : {node | short} : {phase}"
+  @  7 : 4ccc844d5454 : draft
+  |
+  o  6 : f19b7f89f44e : draft
+  |
+  | o  5 : fdc0253c25cf : secret
+  | |
+  | o  4 : b385d13d5ed4 : secret
+  |/
+  o  3 : 14b465a7e25b : draft
+  |
+  o  2 : 0316ce92851d : draft
+  |
+  o  1 : 925d80f479bb : public
+  |
+  o  0 : f7b1eb17ad24 : public
+  
+  $ hg phase --secret --force 1 -n
+  925d80f479bb  1   public -> secret
+  0316ce92851d  2   draft -> secret
+  14b465a7e25b  3   draft -> secret
+  f19b7f89f44e  6   draft -> secret
+  4ccc844d5454  7   draft -> secret
+
+  $ hg phase --public  5 7 -n
+  0316ce92851d  2   draft -> public
+  14b465a7e25b  3   draft -> public
+  f19b7f89f44e  6   draft -> public
+  4ccc844d5454  7   draft -> public
+  b385d13d5ed4  4   secret -> public
+  fdc0253c25cf  5   secret -> public
+
+  $ hg log -G -T "{rev} : {node | short} : {phase}"
+  @  7 : 4ccc844d5454 : draft
+  |
+  o  6 : f19b7f89f44e : draft
+  |
+  | o  5 : fdc0253c25cf : secret
+  | |
+  | o  4 : b385d13d5ed4 : secret
+  |/
+  o  3 : 14b465a7e25b : draft
+  |
+  o  2 : 0316ce92851d : draft
+  |
+  o  1 : 925d80f479bb : public
+  |
+  o  0 : f7b1eb17ad24 : public
+  
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -323,7 +323,7 @@ 
   outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
   parents: rev, style, template
   paths: template
-  phase: public, draft, secret, force, rev
+  phase: public, draft, secret, force, rev, dry-run
   recover: 
   rename: after, force, include, exclude, dry-run
   resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -352,7 +352,7 @@ 
                 _trackphasechange(phasetracking, rev, None, revphase)
         repo.invalidatevolatilesets()
 
-    def advanceboundary(self, repo, tr, targetphase, nodes):
+    def advanceboundary(self, repo, tr, targetphase, nodes, dryrun=None):
         """Set all 'nodes' to phase 'targetphase'
 
         Nodes with a phase lower than 'targetphase' are not affected.
@@ -366,6 +366,13 @@ 
 
         repo = repo.unfiltered()
 
+        rejected = list()
+        changes = [set(), set(), set()]
+        if dryrun:
+            getphase = repo._phasecache.phase
+            rejected = [repo[n].rev() for n in nodes
+                        if getphase(repo, repo[n].rev()) < targetphase]
+
         delroots = [] # set of root deleted by this path
         for phase in xrange(targetphase + 1, len(allphases)):
             # filter nodes that are not in a compatible phase already
@@ -377,47 +384,68 @@ 
             olds = self.phaseroots[phase]
 
             affected = repo.revs('%ln::%ln', olds, nodes)
-            for r in affected:
-                _trackphasechange(phasetracking, r, self.phase(repo, r),
-                                  targetphase)
+            if dryrun:
+                faffected = filter(lambda x: getphase(repo,
+                                                      repo[x].rev()) == phase,
+                                   affected)
+                changes[phase].update(faffected)
+            else:
+                for r in affected:
+                    _trackphasechange(phasetracking, r, self.phase(repo, r),
+                                      targetphase)
 
-            roots = set(ctx.node() for ctx in repo.set(
-                    'roots((%ln::) - %ld)', olds, affected))
-            if olds != roots:
-                self._updateroots(phase, roots, tr)
-                # some roots may need to be declared for lower phases
-                delroots.extend(olds - roots)
-        # declare deleted root in the target phase
-        if targetphase != 0:
-            self._retractboundary(repo, tr, targetphase, delroots)
-        repo.invalidatevolatilesets()
+                roots = set(ctx.node() for ctx in repo.set(
+                        'roots((%ln::) - %ld)', olds, affected))
+                if olds != roots:
+                    self._updateroots(phase, roots, tr)
+                    # some roots may need to be declared for lower phases
+                    delroots.extend(olds - roots)
+        if not dryrun:
+            # declare deleted root in the target phase
+            if targetphase != 0:
+                self._retractboundary(repo, tr, targetphase, delroots)
+            repo.invalidatevolatilesets()
+        return rejected, changes
 
-    def retractboundary(self, repo, tr, targetphase, nodes):
+    def retractboundary(self, repo, tr, targetphase, nodes, dryrun=None):
         oldroots = self.phaseroots[:targetphase + 1]
         if tr is None:
             phasetracking = None
         else:
             phasetracking = tr.changes.get('phases')
         repo = repo.unfiltered()
-        if (self._retractboundary(repo, tr, targetphase, nodes)
-            and phasetracking is not None):
-
-            # find the affected revisions
-            new = self.phaseroots[targetphase]
-            old = oldroots[targetphase]
-            affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
+        changes = [set(), set(), set()]
+        if dryrun:
+            getphase = repo._phasecache.phase
+            nds = [n for n in nodes
+                   if getphase(repo, repo[n].rev()) < targetphase]
+            targetphroots = oldroots[-1]
+            affected = set(repo.revs('(%ln::) - (%ln::)', nds,
+                                     targetphroots))
+            for rev in affected:
+                revphase = getphase(repo, rev)
+                changes[revphase].update((rev,))
+        else:
+            if (self._retractboundary(repo, tr, targetphase, nodes)
+                and phasetracking is not None):
 
-            # find the phase of the affected revision
-            for phase in xrange(targetphase, -1, -1):
-                if phase:
-                    roots = oldroots[phase]
-                    revs = set(repo.revs('%ln::%ld', roots, affected))
-                    affected -= revs
-                else: # public phase
-                    revs = affected
-                for r in revs:
-                    _trackphasechange(phasetracking, r, phase, targetphase)
-        repo.invalidatevolatilesets()
+                # find the affected revisions
+                new = self.phaseroots[targetphase]
+                old = oldroots[targetphase]
+                affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
+
+                # find the phase of the affected revision
+                for phase in xrange(targetphase, -1, -1):
+                    if phase:
+                        roots = oldroots[phase]
+                        revs = set(repo.revs('%ln::%ld', roots, affected))
+                        affected -= revs
+                    else: # public phase
+                        revs = affected
+                    for r in revs:
+                        _trackphasechange(phasetracking, r, phase, targetphase)
+            repo.invalidatevolatilesets()
+        return changes
 
     def _retractboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
@@ -478,28 +506,34 @@ 
         # (see branchmap one)
         self.invalidate()
 
-def advanceboundary(repo, tr, targetphase, nodes):
+def advanceboundary(repo, tr, targetphase, nodes, dryrun=None):
     """Add nodes to a phase changing other nodes phases if necessary.
 
     This function move boundary *forward* this means that all nodes
     are set in the target phase or kept in a *lower* phase.
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.advanceboundary(repo, tr, targetphase, nodes)
-    repo._phasecache.replace(phcache)
+    rejected, changes = phcache.advanceboundary(repo, tr, targetphase, nodes,
+                                                dryrun=dryrun)
+    if not dryrun:
+        repo._phasecache.replace(phcache)
+    return rejected, changes
 
-def retractboundary(repo, tr, targetphase, nodes):
+def retractboundary(repo, tr, targetphase, nodes, dryrun=None):
     """Set nodes back to a phase changing other nodes phases if
     necessary.
 
     This function move boundary *backward* this means that all nodes
     are set in the target phase or kept in a *higher* phase.
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.retractboundary(repo, tr, targetphase, nodes)
-    repo._phasecache.replace(phcache)
+    changes = phcache.retractboundary(repo, tr, targetphase, nodes,
+                                      dryrun=dryrun)
+    if not dryrun:
+        repo._phasecache.replace(phcache)
+    return changes
 
 def registernew(repo, tr, targetphase, nodes):
     """register a new revision and its phase
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3828,7 +3828,7 @@ 
      ('s', 'secret', False, _('set changeset phase to secret')),
      ('f', 'force', False, _('allow to move boundary backward')),
      ('r', 'rev', [], _('target revision'), _('REV')),
-    ],
+    ] + dryrunopts,
     _('[-p|-d|-s] [-f] [-r] [REV...]'))
 def phase(ui, repo, *revs, **opts):
     """set or show the current phase name
@@ -3848,6 +3848,7 @@ 
     (For more information about the phases concept, see :hg:`help phases`.)
     """
     opts = pycompat.byteskwargs(opts)
+    dryrun = opts.get('dry_run')
     # search for a unique phase argument
     targetphase = None
     for idx, name in enumerate(phases.phasenames):
@@ -3868,6 +3869,8 @@ 
 
     ret = 0
     if targetphase is None:
+        if dryrun:
+            raise error.Abort(_("cannot use --dry-run without target phase"))
         # display
         for r in revs:
             ctx = repo[r]
@@ -3883,27 +3886,48 @@ 
             unfi = repo.unfiltered()
             getphase = unfi._phasecache.phase
             olddata = [getphase(unfi, r) for r in unfi]
-            phases.advanceboundary(repo, tr, targetphase, nodes)
+            rejected, advanced = phases.advanceboundary(repo, tr, targetphase,
+                                                        nodes, dryrun=dryrun)
+            if opts['force']:
+                retracted = phases.retractboundary(repo, tr, targetphase,
+                                                       nodes, dryrun=dryrun)
+        if dryrun:
+            changes = advanced[:]
             if opts['force']:
-                phases.retractboundary(repo, tr, targetphase, nodes)
-        getphase = unfi._phasecache.phase
-        newdata = [getphase(unfi, r) for r in unfi]
-        changes = sum(newdata[r] != olddata[r] for r in unfi)
-        cl = unfi.changelog
-        rejected = [n for n in nodes
-                    if newdata[cl.rev(n)] < targetphase]
-        if rejected:
-            ui.warn(_('cannot move %i changesets to a higher '
-                      'phase, use --force\n') % len(rejected))
-            ret = 1
-        if changes:
-            msg = _('phase changed for %i changesets\n') % changes
-            if ret:
-                ui.status(msg)
+                for phase in xrange(3):
+                    changes[phase] = changes[phase].union(retracted[phase])
+                rejected = []
+            if rejected:
+                ui.warn(_('cannot move %i changesets to a higher '
+                          'phase, use --force\n') % len(rejected))
+
+            allphases = ['public', 'draft', 'secret']
+            for phase in xrange(len(allphases)):
+                if changes[phase]:
+                    for rev in changes[phase]:
+                        ui.status(_('{}  {}   {} -> {}\n'.format(
+                            repo[rev].hex()[:12], rev, allphases[phase],
+                            allphases[targetphase])))
+
+        else:
+            getphase = unfi._phasecache.phase
+            newdata = [getphase(unfi, r) for r in unfi]
+            changes = sum(newdata[r] != olddata[r] for r in unfi)
+            cl = unfi.changelog
+            rejected = [n for n in nodes
+                        if newdata[cl.rev(n)] < targetphase]
+            if rejected:
+                ui.warn(_('cannot move %i changesets to a higher '
+                          'phase, use --force\n') % len(rejected))
+                ret = 1
+            if changes:
+                msg = _('phase changed for %i changesets\n') % changes
+                if ret:
+                    ui.status(msg)
+                else:
+                    ui.note(msg)
             else:
-                ui.note(msg)
-        else:
-            ui.warn(_('no phases changed\n'))
+                ui.warn(_('no phases changed\n'))
     return ret
 
 def postincoming(ui, repo, modheads, optupdate, checkout, brev):