[PATCH 1 of 2] branch: introduce --reuse for reusing branches without adding multiple heads

Mads Kiilerich mads at kiilerich.com
Thu Oct 15 09:25:16 UTC 2015


# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1444900755 -7200
#      Thu Oct 15 11:19:15 2015 +0200
# Node ID 5c78d0e30a35d0a2d50148681f3de10470ab088d
# Parent  79d86ab65c9def3fdd65ec972bc5fa89688a19ff
branch: introduce --reuse for reusing branches without adding multiple heads

It is possible to use named branches in ways that are beyond trivial linear
commits on the branch, but where branches still are well-defined and only have
one head. That required unfortunate use of --force.

One example:
Branch heads can be closed and then it makes sense to start the branch again
without basing it on the close commit.

Another example:
Branch heads can be branched to another branch name - then it makes sense to
branch it back to the first branch without merging with the ancestor. This do
especially make sense when named branches are used for development branches and
they are reused to avoid excessive proliferation of branch names.

Thus, introduce --reuse so these safe operations can be done without the scary
"use a bigger hammer" option --force.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1071,9 +1071,10 @@ def bookmark(ui, repo, *names, **opts):
         fm.end()
 
 @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'))],
+    [('r', 'reuse', None, _('allow reuse of closed or ancestor branch heads')),
+     ('f', 'force', None,
+      _('set branch name even if it shadows an existing branch')),
+     ('C', 'clean', None, _('reset branch name to parent branch name'))],
     _('[-fC] [NAME]'))
 def branch(ui, repo, label=None, **opts):
     """set or show the current branch name
@@ -1084,14 +1085,18 @@ def branch(ui, repo, label=None, **opts)
        light-weight bookmark instead. See :hg:`help glossary` for more
        information about named branches and bookmarks.
 
-    With no argument, show the current branch name. With one argument,
-    set the working directory branch name (the branch will not exist
-    in the repository until the next commit). Standard practice
-    recommends that primary development take place on the 'default'
-    branch.
-
-    Unless -f/--force is specified, branch will not let you set a
-    branch name that already exists.
+    With no argument, show the current branch name.
+
+    With one argument, set the working directory branch name (the branch will
+    not exist in the repository until the next commit). Standard practice
+    recommends that primary development take place on the 'default' branch.
+
+    Use --reuse to reuse an existing branch name as long as it doesn't create a
+    new open branch head. It will thus allow reusing a branch name if the
+    existing branch head is an ancestor. It will also allow reusing an existing
+    branch that doesn't have any open heads.
+
+    Use -f/--force to allow creating another head on an existing branch.
 
     Use -C/--clean to reset the working directory branch to that of
     the parent of the working directory, negating a previous branch
@@ -1118,8 +1123,14 @@ def branch(ui, repo, label=None, **opts)
             repo.dirstate.setbranch(label)
             ui.status(_('reset working directory to branch %s\n') % label)
         elif label:
-            if not opts.get('force') and label in repo.branchmap():
-                if label not in [p.branch() for p in repo.parents()]:
+            if label in repo.branchmap():
+                if opts.get('force'):
+                    pass
+                elif (opts.get('reuse') and
+                      (not repo.branchmap().branchheads(label, closed=False)
+                       or repo.revs('%s::parents()', label))):
+                    pass # reviving closed or ancestor branch head
+                elif label not in [p.branch() for p in repo.parents()]:
                     raise error.Abort(_('a branch of the same name already'
                                        ' exists'),
                                      # i18n: "it" refers to an existing branch
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -224,7 +224,7 @@ Show all commands + options
   backout: merge, 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: reuse, force, clean
   branches: active, closed, template
   bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
   cat: output, rev, decode, include, exclude
diff --git a/tests/test-newbranch.t b/tests/test-newbranch.t
--- a/tests/test-newbranch.t
+++ b/tests/test-newbranch.t
@@ -40,6 +40,32 @@ Branch shadowing:
   $ hg ci -m "clear branch name"
   created new head
 
+Branches can be reused on descendents (and thus _not_ creating new branch head)
+or when the branch head has been closed.
+
+  $ hg branch foo
+  abort: a branch of the same name already exists
+  (use 'hg update' to switch to it)
+  [255]
+
+  $ hg branch --reuse foo
+  marked working directory as branch foo
+
+  $ hg up -qC 0
+  $ hg branch --reuse foo
+  abort: a branch of the same name already exists
+  (use 'hg update' to switch to it)
+  [255]
+
+  $ hg up -q foo
+  $ hg ci --close -m 'close foo'
+  $ hg tag -l close-foo
+  $ hg up -qC 0
+  $ hg branch --reuse foo
+  marked working directory as branch foo
+  $ hg up -qC 0
+  $ hg --config extensions.strip= strip -qr close-foo
+
 There should be only one default branch head
 
   $ hg heads .


More information about the Mercurial-devel mailing list