[PATCH] hg tag: add/remove multiple tags; run tag hook only once
John Coomes
John.Coomes at sun.com
Mon Dec 17 18:34:35 CST 2007
Patch to allow 'hg tag' to add or remove multiple tags. Some examples
at
http://www.selenic.com/pipermail/mercurial-devel/2007-December/003667.html
The pretag and tag hooks are run once for each tag. The intent is
that all tags should be added, or none, so if any pretag hook (or
other correctness check) fails no tags are added.
This also fixes a bug which caused the tag hook to be run twice for a
global tag.
# HG changeset patch
# User John Coomes <john.coomes at sun.com>
# Date 1197923354 28800
# Node ID 8851170e9360bfd29b74fd3378d632b1ce6e1240
# Parent 04c76f296ad6f8e32180d6c0fd3982e5faaff3cd
hg tag: add/remove multiple tags; run tag hook only once
- allow multiple tags to be added or removed in one invocation, e.g.,
hg tag -r 42 build-25 beta-1
- remove 'hg tag NAME REV' deprecation warning; that syntax is now
interpreted as 'hg tag NAME1 NAME2'
- run the tag hook for global tags just once per tag, after the commit
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2397,8 +2397,14 @@ def status(ui, repo, *pats, **opts):
if copied:
ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
-def tag(ui, repo, name, rev_=None, **opts):
- """add a tag for the current or given revision
+def _tagabort(singular, plural, badtags):
+ """abort with a message listing the offending tag names"""
+ if len(badtags) == 1:
+ raise util.Abort(singular % badtags[0])
+ raise util.Abort(plural % (", ".join(badtags)))
+
+def tag(ui, repo, name1, *othernames, **opts):
+ """add one or more tags for the current or given revision
Name a particular revision using <name>.
@@ -2415,36 +2421,51 @@ def tag(ui, repo, name, rev_=None, **opt
necessary. The file '.hg/localtags' is used for local tags (not
shared among repositories).
"""
- if name in ['tip', '.', 'null']:
- raise util.Abort(_("the name '%s' is reserved") % name)
- if rev_ is not None:
- ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
- "please use 'hg tag [-r REV] NAME' instead\n"))
- if opts['rev']:
- raise util.Abort(_("use only one form to specify the revision"))
+ rev_ = None
+ names = (name1,) + othernames
+
+ if othernames and len(names) != len({}.fromkeys(names)):
+ badlist = list(names)
+ for key in {}.fromkeys(names):
+ badlist.remove(key)
+ _tagabort(_("the name %s appears more than once"),
+ _("the names %s appear more than once"),
+ ["'" + n + "'" for n in util.unique(badlist)])
+ badlist = ["'" + n + "'" for n in names if n in ['tip', '.', 'null']]
+ if badlist:
+ _tagabort(_("the name %s is reserved"),
+ _("the names %s are reserved"), badlist)
+
if opts['rev'] and opts['remove']:
raise util.Abort(_("--rev and --remove are incompatible"))
if opts['rev']:
rev_ = opts['rev']
message = opts['message']
+ msgplural = (len(names) > 1 and 's') or ''
if opts['remove']:
- if not name in repo.tags():
- raise util.Abort(_('tag %s does not exist') % name)
+ badlist = ["'" + n + "'" for n in names if n not in repo.tags()]
+ if badlist:
+ _tagabort(_('the tag %s does not exist'),
+ _('the tags %s do not exist'), badlist)
rev_ = nullid
if not message:
- message = _('Removed tag %s') % name
- elif name in repo.tags() and not opts['force']:
- raise util.Abort(_('a tag named %s already exists (use -f to force)')
- % name)
+ message = _('Removed tag%s %s') % (msgplural, ", ".join(names))
+ elif not opts['force']:
+ badlist = ["'" + n + "'" for n in names if n in repo.tags()]
+ if badlist:
+ _tagabort(_('the tag %s already exists (use -f to force)'),
+ _('the tags %s already exist (use -f to force)'),
+ badlist)
if not rev_ and repo.dirstate.parents()[1] != nullid:
raise util.Abort(_('uncommitted merge - please provide a '
'specific revision'))
r = repo.changectx(rev_).node()
if not message:
- message = _('Added tag %s for changeset %s') % (name, short(r))
+ message = (_('Added tag%s %s for changeset %s') %
+ (msgplural, ", ".join(names), short(r)))
- repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
+ repo.tag(names, r, message, opts['local'], opts['user'], opts['date'])
def tags(ui, repo):
"""list repository tags
@@ -2938,7 +2959,7 @@ table = {
# -l/--local is already there, commitopts cannot be used
('m', 'message', '', _('use <text> as commit message')),
] + commitopts2,
- _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
+ _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME ...')),
"tags": (tags, [], _('hg tags')),
"tip":
(tip,
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -107,22 +107,25 @@ class localrepository(repo.repository):
tag_disallowed = ':\r\n'
- def _tag(self, name, node, message, local, user, date, parent=None,
+ def _tag(self, names, node, message, local, user, date, parent=None,
extra={}):
use_dirstate = parent is None
+ allnames = "".join(names)
for c in self.tag_disallowed:
- if c in name:
+ if c in allnames:
raise util.Abort(_('%r cannot be used in a tag name') % c)
- self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
+ for name in names:
+ self.hook('pretag', throw=True, node=hex(node), tag=name,
+ local=local)
- def writetag(fp, name, munge, prevtags):
+ def writetags(fp, names, munge, prevtags):
if prevtags and prevtags[-1] != '\n':
fp.write('\n')
- fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
+ for name in names:
+ fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
fp.close()
- self.hook('tag', node=hex(node), tag=name, local=local)
prevtags = ''
if local:
@@ -134,7 +137,9 @@ class localrepository(repo.repository):
prevtags = fp.read()
# local tags are stored in the current charset
- writetag(fp, name, None, prevtags)
+ writetags(fp, names, None, prevtags)
+ for name in names:
+ self.hook('tag', node=hex(node), tag=name, local=local)
return
if use_dirstate:
@@ -154,7 +159,7 @@ class localrepository(repo.repository):
fp.write(prevtags)
# committed tags are stored in UTF-8
- writetag(fp, name, util.fromlocal, prevtags)
+ writetags(fp, names, util.fromlocal, prevtags)
if use_dirstate and '.hgtags' not in self.dirstate:
self.add(['.hgtags'])
@@ -162,12 +167,13 @@ class localrepository(repo.repository):
tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
extra=extra)
- self.hook('tag', node=hex(node), tag=name, local=local)
+ for name in names:
+ self.hook('tag', node=hex(node), tag=name, local=local)
return tagnode
- def tag(self, name, node, message, local, user, date):
- '''tag a revision with a symbolic name.
+ def tag(self, names, node, message, local, user, date):
+ '''tag a revision with one or more symbolic names.
if local is True, the tag is stored in a per-repository file.
otherwise, it is stored in the .hgtags file, and a new
@@ -189,8 +195,7 @@ class localrepository(repo.repository):
raise util.Abort(_('working copy of .hgtags is changed '
'(please commit .hgtags manually)'))
-
- self._tag(name, node, message, local, user, date)
+ self._tag(names, node, message, local, user, date)
def tags(self):
'''return a mapping of tag to node'''
diff --git a/tests/test-globalopts.out b/tests/test-globalopts.out
--- a/tests/test-globalopts.out
+++ b/tests/test-globalopts.out
@@ -181,7 +181,7 @@ list of commands:
serve export the repository via HTTP
showconfig show combined config settings from all hgrc files
status show changed files in the working directory
- tag add a tag for the current or given revision
+ tag add one or more tags for the current or given revision
tags list repository tags
tip show the tip revision
unbundle apply one or more changegroup files
@@ -233,7 +233,7 @@ list of commands:
serve export the repository via HTTP
showconfig show combined config settings from all hgrc files
status show changed files in the working directory
- tag add a tag for the current or given revision
+ tag add one or more tags for the current or given revision
tags list repository tags
tip show the tip revision
unbundle apply one or more changegroup files
diff --git a/tests/test-help.out b/tests/test-help.out
--- a/tests/test-help.out
+++ b/tests/test-help.out
@@ -79,7 +79,7 @@ list of commands:
serve export the repository via HTTP
showconfig show combined config settings from all hgrc files
status show changed files in the working directory
- tag add a tag for the current or given revision
+ tag add one or more tags for the current or given revision
tags list repository tags
tip show the tip revision
unbundle apply one or more changegroup files
@@ -127,7 +127,7 @@ use "hg -v help" to show aliases and glo
serve export the repository via HTTP
showconfig show combined config settings from all hgrc files
status show changed files in the working directory
- tag add a tag for the current or given revision
+ tag add one or more tags for the current or given revision
tags list repository tags
tip show the tip revision
unbundle apply one or more changegroup files
diff --git a/tests/test-hook.out b/tests/test-hook.out
--- a/tests/test-hook.out
+++ b/tests/test-hook.out
@@ -40,7 +40,6 @@ added 3 changesets with 2 changes to 2 f
added 3 changesets with 2 changes to 2 files
(run 'hg update' to get a working copy)
pretag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
-tag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
precommit hook: HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321
pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321
4:8ea2ef7ad3e8
diff --git a/tests/test-tag b/tests/test-tag
--- a/tests/test-tag
+++ b/tests/test-tag
@@ -10,11 +10,21 @@ hg history
echo foo >> .hgtags
hg tag -d "1000000 0" "bleah2" || echo "failed"
-hg tag -d "1000000 0" -r 0 "bleah2" 1 || echo "failed"
hg revert .hgtags
+hg tag -d "1000000 0" -r 0 x y z y y z || echo "failed"
+hg tag -d "1000000 0" tip tap null nada . dot || echo "failed"
+hg tag -d "1000000 0" "bleah" || echo "failed"
+hg tag -d "1000000 0" "bleah" "blecch" || echo "failed"
+
+hg tag -d "1000000 0" --remove "blecch" || echo "failed"
+hg tag -d "1000000 0" --remove "bleah" "blecch" "blough" || echo "failed"
+
hg tag -d "1000000 0" -r 0 "bleah0"
-hg tag -l -d "1000000 0" "bleah1" 1
+hg tag -l -d "1000000 0" -r 1 "bleah1"
+hg tag -d "1000000 0" gack gawk gorp
+hg tag -d "1000000 0" -f gack
+hg tag -d "1000000 0" --remove gack gorp
cat .hgtags
cat .hg/localtags
diff --git a/tests/test-tag.out b/tests/test-tag.out
--- a/tests/test-tag.out
+++ b/tests/test-tag.out
@@ -18,12 +18,26 @@ summary: test
abort: working copy of .hgtags is changed (please commit .hgtags manually)
failed
-use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead
-abort: use only one form to specify the revision
+abort: the names 'y', 'z' appear more than once
failed
-use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead
+abort: the names 'tip', 'null', '.' are reserved
+failed
+abort: the tag 'bleah' already exists (use -f to force)
+failed
+abort: the tag 'bleah' already exists (use -f to force)
+failed
+abort: the tag 'blecch' does not exist
+failed
+abort: the tags 'blecch', 'blough' do not exist
+failed
0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah
0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah0
+868cc8fbb43b754ad09fa109885d243fc49adae7 gack
+868cc8fbb43b754ad09fa109885d243fc49adae7 gawk
+868cc8fbb43b754ad09fa109885d243fc49adae7 gorp
+8990e39091eb986fa5930705ffb2bf68ddbe8133 gack
+0000000000000000000000000000000000000000 gack
+0000000000000000000000000000000000000000 gorp
3ecf002a1c572a2f3bb4e665417e60fca65bbd42 bleah1
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
0acdaf8983679e0aac16e811534eb49d7ee1f2b4 foobar
diff --git a/tests/test-tags.out b/tests/test-tags.out
--- a/tests/test-tags.out
+++ b/tests/test-tags.out
@@ -50,7 +50,7 @@ summary: Removed tag bar
tip 5:57e1983b4a60
% remove nonexistent tag
-abort: tag foobar does not exist
+abort: the tag 'foobar' does not exist
changeset: 5:57e1983b4a60
tag: tip
user: test
@@ -62,7 +62,7 @@ 1 files updated, 0 files merged, 0 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
tip 6:b5ff9d142648
bar 0:b409d9da318e
-abort: a tag named bar already exists (use -f to force)
+abort: the tag 'bar' already exists (use -f to force)
tip 6:b5ff9d142648
bar 0:b409d9da318e
adding foo
More information about the Mercurial-devel
mailing list