D1074: branch: add a --rev flag to change branch name of given revisions

pulkit (Pulkit Goyal) phabricator at mercurial-scm.org
Sat Oct 14 14:42:13 UTC 2017


pulkit created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  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
  
  Tests are added for the same.
  
  .. feature::
  
    An experimental flag `--rev` to `hg branch` which can be used to change
    branch of changesets.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/cmdutil.py
  mercurial/commands.py
  tests/test-branch-change.t
  tests/test-completion.t

CHANGE DETAILS

diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -239,7 +239,7 @@
   backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, extend, command, noupdate
   bookmarks: force, rev, delete, rename, inactive, template
-  branch: force, clean
+  branch: force, clean, rev
   branches: active, closed, template
   bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
   cat: output, rev, decode, include, exclude, template
diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t
new file mode 100644
--- /dev/null
+++ b/tests/test-branch-change.t
@@ -0,0 +1,317 @@
+Testing changing branch on commits
+==================================
+
+Setup
+----
+
+  $ cat >> $HGRCPATH << EOF
+  > [alias]
+  > glog = log -G -T "{rev}:{node|short} {desc}\n{branch} ({bookmarks})"
+  > [experimental]
+  > evolution = createmarkers
+  > [extensions]
+  > rebase=
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d e f g h; do echo foo >> $ch; hg ci -Aqm "Added "$ch; done
+  $ hg glog
+  @  7:ec2426147f0e Added h
+  |  default ()
+  o  6:87d6d6676308 Added g
+  |  default ()
+  o  5:825660c69f0c Added f
+  |  default ()
+  o  4:aa98ab95a928 Added e
+  |  default ()
+  o  3:62615734edd5 Added d
+  |  default ()
+  o  2:28ad74487de9 Added c
+  |  default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+  $ hg branches
+  default                        7:ec2426147f0e
+
+Try without passing a new branch name
+-----------------------------------------
+
+  $ hg branch -r 5::7
+  abort: no branch name specified for the revisions
+  [255]
+
+Setting an invalid branch name
+-------------------------------------
+
+  $ hg branch -r 5::7 a:b
+  abort: ':' cannot be used in a name
+  [255]
+  $ hg branch -r 5::7 tip
+  abort: the name 'tip' is reserved
+  [255]
+  $ hg branch -r 5::7 1234
+  abort: cannot use an integer as a name
+  [255]
+
+Change on non-linear set of commits
+--------------------------------------------
+
+  $ hg branch -r 4 -r 6 foo
+  abort: cannot change branch of non-linear revisions
+  [255]
+
+Change in between the stack (linear commits)
+------------------------------------------------------
+
+  $ hg branch -r 4::6 foo
+  abort: cannot change branch in betwen the stack
+  [255]
+
+Changing branch on linear set of commits from head
+--------------------------------------------------
+
+Without obsmarkers
+
+  $ hg branch -r 5::7 foo --config experimental.evolution=!
+  changed branch on 3 changesets
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/825660c69f0c-ce9f7a94-branch-change.hg (glob)
+  $ hg glog
+  @  7:c23036697d1b Added h
+  |  foo ()
+  o  6:a53c3f56770a Added g
+  |  foo ()
+  o  5:ff1da3b38f9e Added f
+  |  foo ()
+  o  4:aa98ab95a928 Added e
+  |  default ()
+  o  3:62615734edd5 Added d
+  |  default ()
+  o  2:28ad74487de9 Added c
+  |  default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+  $ hg branches
+  foo                            7:c23036697d1b
+  default                        4:aa98ab95a928 (inactive)
+
+With obsmarkers
+
+  $ hg branch -r 5::7 bar
+  changed branch on 3 changesets
+  $ hg glog
+  @  10:e6dd2bf0e93e Added h
+  |  bar ()
+  o  9:b71d0e6b76ec Added g
+  |  bar ()
+  o  8:e47e2354372c Added f
+  |  bar ()
+  o  4:aa98ab95a928 Added e
+  |  default ()
+  o  3:62615734edd5 Added d
+  |  default ()
+  o  2:28ad74487de9 Added c
+  |  default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+  $ hg branches
+  bar                           10:e6dd2bf0e93e
+  default                        4:aa98ab95a928 (inactive)
+
+Change branch name to an existing branch
+-------------------------------------------
+
+  $ hg branch -r . default
+  abort: a branch of the same name already exists
+  (use 'hg update' to switch to it)
+  [255]
+
+Make sure bookmark movement is correct
+----------------------------------------
+
+  $ hg bookmark b1
+  $ hg glog -r .
+  @  10:e6dd2bf0e93e Added h
+  |  bar (b1)
+  ~
+  $ hg branch -r '(.^)::' foo
+  changed branch on 2 changesets
+  $ hg glog -r .
+  @  12:03fd98f490cd Added h
+  |  foo (b1)
+  ~
+  $ hg glog
+  @  12:03fd98f490cd Added h
+  |  foo (b1)
+  o  11:6bcbfdc170f7 Added g
+  |  foo ()
+  o  8:e47e2354372c Added f
+  |  bar ()
+  o  4:aa98ab95a928 Added e
+  |  default ()
+  o  3:62615734edd5 Added d
+  |  default ()
+  o  2:28ad74487de9 Added c
+  |  default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+Make sure phase handling is correct
+------------------------------------
+
+  $ echo foo >> bar
+  $ hg ci -Aqm "added bar" --secret
+  $ hg glog -r .
+  @  13:f11139b5413b added bar
+  |  foo (b1)
+  ~
+  $ hg branch -r . secret
+  changed branch on 1 changesets
+  $ hg phase -r .
+  14: secret
+  $ hg branches
+  secret                        14:42e97792ed5d
+  foo                           12:03fd98f490cd (inactive)
+  bar                            8:e47e2354372c (inactive)
+  default                        4:aa98ab95a928 (inactive)
+  $ hg branch
+  secret
+
+Changing branch of another head, different from one on which we are
+-------------------------------------------------------------------
+
+  $ hg rebase -s 3 -d 1 -q --keepbranches
+  $ hg glog
+  @  20:4312b52874e6 added bar
+  |  secret (b1)
+  o  19:d8a0d829626c Added h
+  |  foo ()
+  o  18:a3992cbc5da1 Added g
+  |  foo ()
+  o  17:271f592ffeb1 Added f
+  |  bar ()
+  o  16:21bf4f045390 Added e
+  |  default ()
+  o  15:a032b5424026 Added d
+  |  default ()
+  | o  2:28ad74487de9 Added c
+  |/   default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+  $ hg branches
+  secret                        20:4312b52874e6
+  default                       16:21bf4f045390
+  foo                           19:d8a0d829626c (inactive)
+  bar                           17:271f592ffeb1 (inactive)
+  $ hg branch
+  secret
+
+  $ hg branch -r 2 foobar
+  changed branch on 1 changesets
+  $ hg glog
+  o  21:13c2545aa399 Added c
+  |  foobar ()
+  | @  20:4312b52874e6 added bar
+  | |  secret (b1)
+  | o  19:d8a0d829626c Added h
+  | |  foo ()
+  | o  18:a3992cbc5da1 Added g
+  | |  foo ()
+  | o  17:271f592ffeb1 Added f
+  | |  bar ()
+  | o  16:21bf4f045390 Added e
+  | |  default ()
+  | o  15:a032b5424026 Added d
+  |/   default ()
+  o  1:29becc82797a Added b
+  |  default ()
+  o  0:18d04c59bb5d Added a
+     default ()
+
+  $ hg branches
+  foobar                        21:13c2545aa399
+  secret                        20:4312b52874e6
+  foo                           19:d8a0d829626c (inactive)
+  bar                           17:271f592ffeb1 (inactive)
+  default                       16:21bf4f045390 (inactive)
+The current branch must be preserved
+  $ hg branch
+  secret
+
+Changing branch on mutliple heads at once
+-----------------------------------------
+
+  $ hg branch -r 1: wat
+  changed branch on 8 changesets
+  $ hg glog
+  o  29:96deb8de5734 Added c
+  |  wat ()
+  | @  28:90f2960c3410 added bar
+  | |  wat (b1)
+  | o  27:a285fc5cd0b9 Added h
+  | |  wat ()
+  | o  26:6906ec2d36a8 Added g
+  | |  wat ()
+  | o  25:00fbab1bf47f Added f
+  | |  wat ()
+  | o  24:1a764b704348 Added e
+  | |  wat ()
+  | o  23:5d2e3635fbc0 Added d
+  |/   wat ()
+  o  22:aa56aac9964f Added b
+  |  wat ()
+  o  0:18d04c59bb5d Added a
+     default ()
+  $ hg branches
+  wat                           29:96deb8de5734
+  default                        0:18d04c59bb5d (inactive)
+
+  $ hg branch
+  wat
+
+Changing branch on public changeset
+-----------------------------------
+
+  $ hg phase -r 29 -p
+  $ hg branch -r 29 stable
+  abort: cannot change branch of public revisions
+  [255]
+
+Changing to same branch name is no-op
+------------------------------------
+
+  $ hg branch -r 23::28 wat
+  changed branch on 0 changesets
+  $ hg glog
+  o  29:96deb8de5734 Added c
+  |  wat ()
+  | @  28:90f2960c3410 added bar
+  | |  wat (b1)
+  | o  27:a285fc5cd0b9 Added h
+  | |  wat ()
+  | o  26:6906ec2d36a8 Added g
+  | |  wat ()
+  | o  25:00fbab1bf47f Added f
+  | |  wat ()
+  | o  24:1a764b704348 Added e
+  | |  wat ()
+  | o  23:5d2e3635fbc0 Added d
+  |/   wat ()
+  o  22:aa56aac9964f Added b
+  |  wat ()
+  o  0:18d04c59bb5d Added a
+     default ()
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -993,7 +993,8 @@
 @command('branch',
     [('f', 'force', None,
      _('set branch name even if it shadows an existing branch')),
-    ('C', 'clean', None, _('reset branch name to parent branch name'))],
+    ('C', 'clean', None, _('reset branch name to parent branch name')),
+    ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)'))],
     _('[-fC] [NAME]'))
 def branch(ui, repo, label=None, **opts):
     """set or show the current branch name
@@ -1025,10 +1026,13 @@
     Returns 0 on success.
     """
     opts = pycompat.byteskwargs(opts)
+    revs = opts.get('rev')
     if label:
         label = label.strip()
 
     if not opts.get('clean') and not label:
+        if revs:
+            raise error.Abort("no branch name specified for the revisions")
         ui.write("%s\n" % repo.dirstate.branch())
         return
 
@@ -1045,7 +1049,9 @@
                                      # i18n: "it" refers to an existing branch
                                      hint=_("use 'hg update' to switch to it"))
             scmutil.checknewlabel(repo, label, 'branch')
-            if True:
+            if revs:
+                cmdutil.changebranch(ui, repo, revs, label)
+            else:
                 repo.dirstate.setbranch(label)
                 ui.status(_('marked working directory as branch %s\n') % label)
 
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -35,6 +35,7 @@
     obsolete,
     patch,
     pathutil,
+    phases,
     pycompat,
     registrar,
     revlog,
@@ -733,6 +734,87 @@
 
     raise error.UnknownCommand(cmd, allcmds)
 
+def changebranch(ui, repo, revs, label):
+    """ Change the branch name of given revs to label """
+
+    revs = scmutil.revrange(repo, revs)
+    roots = repo.revs('roots(%ld)', revs)
+    if len(roots) > 1:
+        raise error.Abort(_("cannot change branch of non-linear revisions"))
+    root = repo[roots.first()]
+    if root.phase() <= phases.public:
+        raise error.Abort(_("cannot change branch of public revisions"))
+    heads = repo.revs('head() and %ld', revs)
+    if len(heads) < 1:
+        raise error.Abort(_("cannot change branch in betwen the stack"))
+
+    replacements = {}
+    rewrote = 0
+    with repo.wlock(), repo.lock(), repo.transaction('branches'):
+        # 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'" % (
+                            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)
+            # phase handling
+            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))
+            rewrote += 1
+
+        # 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") % rewrote)
+
 def findrepo(p):
     while not os.path.isdir(os.path.join(p, ".hg")):
         oldp, p = p, os.path.dirname(p)



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


More information about the Mercurial-devel mailing list