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): 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,7 +506,7 @@ # (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 @@ -486,10 +514,13 @@ 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. @@ -498,8 +529,11 @@ 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/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/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 +