[PATCH rfc] branches: introduce closing of other heads on other named branches

Mads Kiilerich mads at kiilerich.com
Sun Oct 25 00:02:28 UTC 2015


# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1445731342 -7200
#      Sun Oct 25 02:02:22 2015 +0200
# Branch stable
# Node ID ee590daabd2fe53685f14289db72aa03faeb2cb1
# Parent  39dbf495880b8a439d912091109427d27a7e616a
branches: introduce closing of other heads on other named branches

Closing of named branches was problematic. Especially when a lot of branches
were used and there was a need for closing them so they no longer showed up.
Closing a branch required a commit on the branch. That created an extra head on
the branch. To make sure that users who had the changesets from the branch also
had the close commit, the close had to be done before merging anywhere, or an
additional merge had to be done. That became even more troublesome when
branches were nested so merging one branch actually also introduced several
other unclosed branch heads in the ancestry. The extra commits on branches
could also often end up causing multiple heads on the branches if development
was continued elsewhere anyway.

Instead, make it possible for commits to also mark existing commits as closed.
With --other-close REVSET, all the branch heads in that revset will be marked
as closed in meta data of the commit.

For general commits of closing all ancestor branch heads, use something like:
  hg commit --other-close "::parents()"

For more efficient closing of just the branches that has been merged, use
something like:
  hg commit --other-close "only(p2(),p1())"

diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -127,9 +127,10 @@ def replacecache(repo, bm):
         rbheads.extend(bheads)
         for h in bheads:
             r = repo.changelog.rev(h)
-            b, c = repo.changelog.branchinfo(r)
+            b, c, closeheads = repo.changelog.branchinfo(r)
             if c:
                 closed.append(h)
+            closed.extend(closeheads)
 
     if rbheads:
         rtiprev = max((int(repo.changelog.rev(node))
@@ -266,10 +267,11 @@ class branchcache(dict):
         newbranches = {}
         getbranchinfo = repo.revbranchcache().branchinfo
         for r in revgen:
-            branch, closesbranch = getbranchinfo(r)
+            branch, closesbranch, closednodes = getbranchinfo(r)
             newbranches.setdefault(branch, []).append(r)
             if closesbranch:
                 self._closednodes.add(cl.node(r))
+            self._closednodes.update(closednodes)
 
         # fetch current topological heads to speed up filtering
         topoheads = set(cl.headrevs())
@@ -407,7 +409,7 @@ class revbranchcache(object):
         if cachenode == '\0\0\0\0':
             pass
         elif cachenode == reponode:
-            return self._names[branchidx], close
+            return self._names[branchidx], close, []
         else:
             # rev/node map has changed, invalidate the cache from here up
             truncate = rbcrevidx + _rbcrecsize
@@ -420,7 +422,7 @@ class revbranchcache(object):
     def _branchinfo(self, rev):
         """Retrieve branch info from changelog and update _rbcrevs"""
         changelog = self._repo.changelog
-        b, close = changelog.branchinfo(rev)
+        b, close, closeheads = changelog.branchinfo(rev)
         if b in self._namesreverse:
             branchidx = self._namesreverse[b]
         else:
@@ -431,7 +433,7 @@ class revbranchcache(object):
         if close:
             branchidx |= _rbccloseflag
         self._setcachedata(rev, reponode, branchidx)
-        return b, close
+        return b, close, closeheads
 
     def _setcachedata(self, rev, node, branchidx):
         """Writes the node's branch data to the in-memory cache data."""
diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -403,4 +403,5 @@ class changelog(revlog.revlog):
         This function exists because creating a changectx object
         just to access this is costly."""
         extra = self.read(rev)[5]
-        return encoding.tolocal(extra.get("branch")), 'close' in extra
+        return (encoding.tolocal(extra.get("branch")), 'close' in extra,
+            (bin(v) for k, v in extra.items() if k.startswith('close-')))
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1459,6 +1459,8 @@ def clone(ui, source, dest=None, **opts)
      _('mark new/missing files as added/removed before committing')),
     ('', 'close-branch', None,
      _('mark a branch head as closed')),
+    ('', 'other-close', [],
+     _('mark branch heads of revset as closed'), _('REV')),
     ('', 'amend', None, _('amend the parent of the working directory')),
     ('s', 'secret', None, _('use the secret phase for committing')),
     ('e', 'edit', None, _('invoke editor on commit messages')),
@@ -1534,6 +1536,18 @@ def commit(ui, repo, *pats, **opts):
                     repo.parents()[0].p2().branch() != branch:
                 raise error.Abort(_('can only close branch heads'))
 
+    if opts.get('other_close'):
+        revs = scmutil.revrange(repo, opts['other_close'])
+        i = 0
+        for b in sorted(set([repo[r].branch() for r in revs])):
+            if b == branch:
+                continue
+            for bh in repo.revs('heads(branch(%s)&%ld)&!closed()', b, revs):
+                n = 'close-%s' % i
+                extra[n] = repo[bh].hex()
+                i += 1
+                ui.debug('closing branch %s head %s\n' % (b, bh))
+
     if opts.get('amend'):
         if ui.configbool('ui', 'commitsubrepos'):
             raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1544,7 +1544,8 @@ class localrepository(object):
 
             # internal config: ui.allowemptycommit
             allowemptycommit = (wctx.branch() != wctx.p1().branch()
-                                or extra.get('close') or merge or cctx.files()
+                                or extra.get('close') or extra.get('close-0')
+                                or merge or cctx.files()
                                 or self.ui.configbool('ui', 'allowemptycommit'))
             if not allowemptycommit:
                 return None
diff --git a/tests/test-branches.t b/tests/test-branches.t
--- a/tests/test-branches.t
+++ b/tests/test-branches.t
@@ -629,4 +629,51 @@ situation where the cache is out of sync
   $ f --size .hg/cache/rbc-revs*
   .hg/cache/rbc-revs-v1: size=112
 
+Test closing of other branches
+
+  $ hg branch -q branch-a
+  $ hg ci -mbranch-a
+  $ hg branch -q branch-b
+  $ hg ci -mbranch-b
+  $ hg branch -q branch-c
+  $ hg ci -mbranch-c
+  $ hg ci --other-close '::parents()+7+a' -m abracadabra --debug
+  closing branch a head 5
+  closing branch a branch name much longer than the default justification used by branches head 7
+  closing branch b head 13
+  closing branch branch-a head 14
+  closing branch branch-b head 15
+  closing branch default head 0
+  committing changelog
+  committed changeset 17:d1e05643cd878866aea570fdc0d2a579388053c8
+  $ hg par --debug
+  changeset:   17:d1e05643cd878866aea570fdc0d2a579388053c8
+  branch:      branch-c
+  tag:         tip
+  phase:       draft
+  parent:      16:bc4f9e9f8e06b3ba57f872e638c2926f92d8de6b
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    11:10adc210c59a47a58e58fe0681352b43807e4406
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  extra:       branch=branch-c
+  extra:       close-0=d8cbc61dbaa6dc817175d1e301eecb863f280832
+  extra:       close-1=10ff5895aa5793bd378da574af8cec8ea408d831
+  extra:       close-2=e23b5505d1ad24aab6f84fd8c7cb8cd8e5e93be0
+  extra:       close-3=47b830e76b2005391e03d0cd4eec1987f385d5bc
+  extra:       close-4=32a84e5f6d7128fcc5a7eea92ede48f9dc6bc693
+  extra:       close-5=19709c5a4e75bf938f8e349aff97438539bb729e
+  description:
+  abracadabra
+  
+  
+  $ hg branches --closed
+  branch-c                      17:d1e05643cd87
+  branch-b                      15:32a84e5f6d71 (closed)
+  branch-a                      14:47b830e76b20 (closed)
+  b                             13:e23b5505d1ad (closed)
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57 (closed)
+  c                              6:589736a22561 (inactive)
+  a                              5:d8cbc61dbaa6 (closed)
+  default                        0:19709c5a4e75 (closed)
   $ cd ..
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -207,7 +207,7 @@ Show all commands + options
   add: include, exclude, subrepos, dry-run
   annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
-  commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
+  commit: addremove, close-branch, other-close, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
   diff: rev, change, text, git, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, root, include, exclude, subrepos
   export: output, switch-parent, rev, text, git, nodates
   forget: include, exclude
diff --git a/tests/test-qrecord.t b/tests/test-qrecord.t
--- a/tests/test-qrecord.t
+++ b/tests/test-qrecord.t
@@ -59,6 +59,7 @@ help record (record)
    -A --addremove           mark new/missing files as added/removed before
                             committing
       --close-branch        mark a branch head as closed
+      --other-close REV [+] mark branch heads of revset as closed
       --amend               amend the parent of the working directory
    -s --secret              use the secret phase for committing
    -e --edit                invoke editor on commit messages
diff --git a/tests/test-record.t b/tests/test-record.t
--- a/tests/test-record.t
+++ b/tests/test-record.t
@@ -46,6 +46,7 @@ Record help
    -A --addremove           mark new/missing files as added/removed before
                             committing
       --close-branch        mark a branch head as closed
+      --other-close REV [+] mark branch heads of revset as closed
       --amend               amend the parent of the working directory
    -s --secret              use the secret phase for committing
    -e --edit                invoke editor on commit messages


More information about the Mercurial-devel mailing list