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

login
register
mail settings
Submitter phabricator
Date Oct. 16, 2017, 12:31 p.m.
Message ID <6d182310779e5fc41a4d75e16367ab62@localhost.localdomain>
Download mbox | patch
Permalink /patch/24974/
State Not Applicable
Headers show

Comments

phabricator - Oct. 16, 2017, 12:31 p.m.
pulkit updated this revision to Diff 2807.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D1074?vs=2789&id=2807

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




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

Patch

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,324 @@ 
+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 middle of the stack (linear commits)
+
+  $ hg branch -r 4::6 foo
+  abort: cannot change branch in middle of a 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 --debug
+  changing branch of 'b71d0e6b76ecd9179149fd19962019ea505c099c' from 'bar' to 'foo'
+  committing files:
+  g
+  committing manifest
+  committing changelog
+  new node id is 6bcbfdc170f74a3647b3afa24fdba997883750df
+  changing branch of 'e6dd2bf0e93e140e9b185d8df7d2fd55b01c270a' from 'bar' to 'foo'
+  committing files:
+  h
+  committing manifest
+  committing changelog
+  new node id is 03fd98f490cd52c2e3ec48b5f1aa29eb1cfe7b90
+  moving bookmarks ['b1'] from e6dd2bf0e93e140e9b185d8df7d2fd55b01c270a to 03fd98f490cd52c2e3ec48b5f1aa29eb1cfe7b90
+  resolving manifests
+   branchmerge: False, force: False, partial: False
+   ancestor: e6dd2bf0e93e, local: e6dd2bf0e93e+, remote: 03fd98f490cd
+  changed branch on 2 changesets
+  updating the branch cache
+  invalid branchheads cache (served): tip differs
+
+  $ 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 multiple 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,9 @@ 
 @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 +1027,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,6 +1050,9 @@ 
                                      # i18n: "it" refers to an existing branch
                                      hint=_("use 'hg update' to switch to it"))
             scmutil.checknewlabel(repo, label, 'branch')
+            if revs:
+                return cmdutil.changebranch(ui, repo, revs, label)
+
             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,
@@ -715,6 +716,85 @@ 
 
     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 middle of a stack"))
+
+    replacements = {}
+    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'\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)