diff mercurial/cmdutil.py @ 35745:3bd8ab4c80a5

branch: add a --rev flag to change branch name of given revisions This patch adds a new --rev flag to hg branch which can be used to change branch of revisions. This is motivated from topic extension where you can change topic on revisions but this one has few restrictions which are: 1) You cannot change branch name in between the stack 2) You cannot change branch name and set it to an existing name 3) You cannot change branch of non-linear set of commits 4) You cannot change branch of merge commits. Tests are added for the same. .. feature:: An experimental flag `--rev` to `hg branch` which can be used to change branch of changesets. Differential Revision: https://phab.mercurial-scm.org/D1074
author Pulkit Goyal <7895pulkit@gmail.com>
date Sun, 15 Oct 2017 23:08:45 +0530
parents 3c2a6246fd63
children e5b6ba786d83
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Tue Jan 16 23:50:01 2018 +0900
+++ b/mercurial/cmdutil.py	Sun Oct 15 23:08:45 2017 +0530
@@ -42,6 +42,7 @@
     revlog,
     revset,
     revsetlang,
+    rewriteutil,
     scmutil,
     smartset,
     templatekw,
@@ -713,6 +714,92 @@
 
     raise error.UnknownCommand(cmd, allcmds)
 
+def changebranch(ui, repo, revs, label):
+    """ Change the branch name of given revs to label """
+
+    with repo.wlock(), repo.lock(), repo.transaction('branches'):
+        # abort in case of uncommitted merge or dirty wdir
+        bailifchanged(repo)
+        revs = scmutil.revrange(repo, revs)
+        if not revs:
+            raise error.Abort("empty revision set")
+        roots = repo.revs('roots(%ld)', revs)
+        if len(roots) > 1:
+            raise error.Abort(_("cannot change branch of non-linear revisions"))
+        rewriteutil.precheck(repo, revs, 'change branch of')
+        if repo.revs('merge() and %ld', revs):
+            raise error.Abort(_("cannot change branch of a merge commit"))
+        if repo.revs('obsolete() and %ld', revs):
+            raise error.Abort(_("cannot change branch of a obsolete changeset"))
+
+        # make sure only topological heads
+        if repo.revs('heads(%ld) - head()', revs):
+            raise error.Abort(_("cannot change branch in middle of a stack"))
+
+        replacements = {}
+        # avoid import cycle mercurial.cmdutil -> mercurial.context ->
+        # mercurial.subrepo -> mercurial.cmdutil
+        from . import context
+        for rev in revs:
+            ctx = repo[rev]
+            oldbranch = ctx.branch()
+            # check if ctx has same branch
+            if oldbranch == label:
+                continue
+
+            def filectxfn(repo, newctx, path):
+                try:
+                    return ctx[path]
+                except error.ManifestLookupError:
+                    return None
+
+            ui.debug("changing branch of '%s' from '%s' to '%s'\n"
+                     % (hex(ctx.node()), oldbranch, label))
+            extra = ctx.extra()
+            extra['branch_change'] = hex(ctx.node())
+            # While changing branch of set of linear commits, make sure that
+            # we base our commits on new parent rather than old parent which
+            # was obsoleted while changing the branch
+            p1 = ctx.p1().node()
+            p2 = ctx.p2().node()
+            if p1 in replacements:
+                p1 = replacements[p1][0]
+            if p2 in replacements:
+                p2 = replacements[p2][0]
+
+            mc = context.memctx(repo, (p1, p2),
+                                ctx.description(),
+                                ctx.files(),
+                                filectxfn,
+                                user=ctx.user(),
+                                date=ctx.date(),
+                                extra=extra,
+                                branch=label)
+
+            commitphase = ctx.phase()
+            overrides = {('phases', 'new-commit'): commitphase}
+            with repo.ui.configoverride(overrides, 'branch-change'):
+                newnode = repo.commitctx(mc)
+
+            replacements[ctx.node()] = (newnode,)
+            ui.debug('new node id is %s\n' % hex(newnode))
+
+        # create obsmarkers and move bookmarks
+        scmutil.cleanupnodes(repo, replacements, 'branch-change')
+
+        # move the working copy too
+        wctx = repo[None]
+        # in-progress merge is a bit too complex for now.
+        if len(wctx.parents()) == 1:
+            newid = replacements.get(wctx.p1().node())
+            if newid is not None:
+                # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
+                # mercurial.cmdutil
+                from . import hg
+                hg.update(repo, newid[0], quietempty=True)
+
+        ui.status(_("changed branch on %d changesets\n") % len(replacements))
+
 def findrepo(p):
     while not os.path.isdir(os.path.join(p, ".hg")):
         oldp, p = p, os.path.dirname(p)