extensions: new closehead module for closing arbitrary heads
``hg close-head`` allows closing arbitrary heads. It is equivalent to
checking out the given revisions and commit an empty change with
``hg commit --close-branch``.
Differential Revision: https://phab.mercurial-scm.org/D3557
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/closehead.py Mon May 14 00:43:07 2018 +0200
@@ -0,0 +1,82 @@
+# closehead.py - Close arbitrary heads without checking them out first
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''close arbitrary heads without checking them out first'''
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+ bookmarks,
+ cmdutil,
+ context,
+ error,
+ pycompat,
+ registrar,
+ scmutil,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+commitopts = cmdutil.commitopts
+commitopts2 = cmdutil.commitopts2
+commitopts3 = [('r', 'rev', '',
+ _('revision to check'), _('REV'))]
+
+@command('close-head|close-heads', commitopts + commitopts2 + commitopts3,
+ _('[OPTION]... [REV]...'), inferrepo=True)
+def close_branch(ui, repo, *revs, **opts):
+ """close the given head revisions
+
+ This is equivalent to checking out each revision in a clean tree and running
+ ``hg commit --close-branch``, except that it doesn't change the working
+ directory.
+
+ The commit message must be specified with -l or -m.
+ """
+ def docommit(rev):
+ cctx = context.memctx(repo, parents=[rev, None], text=message,
+ files=[], filectxfn=None, user=opts.get('user'),
+ date=opts.get('date'), extra=extra)
+ tr = repo.transaction('commit')
+ ret = repo.commitctx(cctx, True)
+ bookmarks.update(repo, [rev, None], ret)
+ cctx.markcommitted(ret)
+ tr.close()
+
+ opts = pycompat.byteskwargs(opts)
+
+ revs += tuple(opts.get('rev', []))
+ revs = scmutil.revrange(repo, revs)
+
+ if not revs:
+ raise error.Abort(_('no revisions specified'))
+
+ heads = []
+ for branch in repo.branchmap():
+ heads.extend(repo.branchheads(branch))
+ heads = set(repo[h].rev() for h in heads)
+ for rev in revs:
+ if rev not in heads:
+ raise error.Abort(_('revision is not an open head: %s') % rev)
+
+ message = cmdutil.logmessage(ui, opts)
+ if not message:
+ raise error.Abort(_("no commit message specified with -l or -m"))
+ extra = { 'close': '1' }
+
+ with repo.wlock(), repo.lock():
+ for rev in revs:
+ r = repo[rev]
+ branch = r.branch()
+ extra['branch'] = branch
+ docommit(r)
+ return 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-close-head.t Mon May 14 00:43:07 2018 +0200
@@ -0,0 +1,61 @@
+ $ hg init test-content
+ $ cd test-content
+ $ hg debugbuilddag '+2*2*3*4'
+ $ hg bookmark -r 1 @
+ $ hg log -G --template '{rev}:{node|short}'
+ o 4:e7bd5218ca15
+ |
+ | o 3:6100d3090acf
+ |/
+ | o 2:fa942426a6fd
+ |/
+ | o 1:66f7d451a68b
+ |/
+ o 0:1ea73414a91b
+
+ $ hg --config extensions.closehead= close-head -m 'Not a head' 0 1
+ abort: revision is not an open head: 0
+ [255]
+ $ hg --config extensions.closehead= close-head -m 'Not a head' -r 0 1
+ abort: revision is not an open head: 0
+ [255]
+ $ hg --config extensions.closehead= close-head -m 'Close old heads' -r 1 2
+ $ hg bookmark
+ @ 1:66f7d451a68b
+ $ hg heads
+ changeset: 4:e7bd5218ca15
+ parent: 0:1ea73414a91b
+ user: debugbuilddag
+ date: Thu Jan 01 00:00:04 1970 +0000
+ summary: r4
+
+ changeset: 3:6100d3090acf
+ parent: 0:1ea73414a91b
+ user: debugbuilddag
+ date: Thu Jan 01 00:00:03 1970 +0000
+ summary: r3
+
+ $ hg --config extensions.closehead= close-head -m 'Close more old heads' 4
+ $ hg heads
+ changeset: 3:6100d3090acf
+ parent: 0:1ea73414a91b
+ user: debugbuilddag
+ date: Thu Jan 01 00:00:03 1970 +0000
+ summary: r3
+
+ $ hg --config extensions.closehead= close-head -m 'Not a head' 0
+ abort: revision is not an open head: 0
+ [255]
+ $ hg --config extensions.closehead= close-head -m 'Already closed head' 1
+ abort: revision is not an open head: 1
+ [255]
+
+ $ hg init ../test-empty
+ $ cd ../test-empty
+ $ hg debugbuilddag '+1'
+ $ hg log -G --template '{rev}:{node|short}'
+ o 0:1ea73414a91b
+
+ $ hg --config extensions.closehead= close-head -m 'Close initial revision' 0
+ $ hg heads
+ [1]
--- a/tests/test-help.t Tue Oct 02 13:12:56 2018 -0700
+++ b/tests/test-help.t Mon May 14 00:43:07 2018 +0200
@@ -262,6 +262,7 @@
censor erase file content at a given revision
churn command to display statistics about repository history
clonebundles advertise pre-generated bundles to seed clones
+ closehead close arbitrary heads without checking them out first
convert import revisions from foreign VCS repositories into
Mercurial
eol automatically manage newlines in repository files