@@ -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
new file mode 100644
@@ -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 ()
@@ -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)
@@ -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)