[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