[PATCH] tag: allow multiple tags to be added or removed

John Coomes John.Coomes at sun.com
Sat Feb 23 01:22:44 CST 2008


Patch to allow multiple tags to be added/removed in a single invocation
of hg tag.  Basic example of the new usage is

	hg tag -r 42 build-25 beta-1

which adds tags 'build-25' and 'beta-1' for rev 42.

Worth noting:

- This changes the meaning of deprecated, but accepted, usage.
  The undocumented form "hg tag arg1 arg2" used to emit a warning, then
  add tag arg1 for rev arg2 (equivalent to "hg tag -r arg2 arg1").  That
  form will now add tags arg1 and arg2 for the current revision.
- If one tag triggers an error, no tags are added/removed (all or nothing).
- When aborting, complain about all tag names with errors instead of just the
  first one.  This led to (mostly trivial) code to deal w/different
  messages for single-tag vs. multiple-tag errors.

There is some email from December (had to put this on hold for a while):

http://www.selenic.com/pipermail/mercurial-devel/2007-December/003648.html
http://www.selenic.com/pipermail/mercurial-devel/2007-December/003655.html
http://www.selenic.com/pipermail/mercurial-devel/2007-December/003667.html

http://www.selenic.com/pipermail/mercurial-devel/2007-December/003820.html
http://www.selenic.com/pipermail/mercurial-devel/2007-December/003825.html
http://www.selenic.com/pipermail/mercurial-devel/2007-December/003828.html

-John


# HG changeset patch
# User John Coomes <john.coomes at sun.com>
# Date 1203750406 28800
# Node ID fa824ac29642ff702c4d920612ca5ea90135ab75
# Parent  f857eac30cd54fc2249c8044388718687df7bed5
tag: allow multiple tags to be added or removed

- Example:  "hg tag -r 42 build-25 beta-1" would add two tags for rev 42
- remove "hg tag NAME REV" deprecation warning; that syntax is now
  interpreted as "hg tag NAME1 NAME2"

diff -r f857eac30cd5 -r fa824ac29642 mercurial/commands.py
--- a/mercurial/commands.py	Thu Feb 21 20:56:06 2008 +0100
+++ b/mercurial/commands.py	Fri Feb 22 23:06:46 2008 -0800
@@ -2532,13 +2532,13 @@
                 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 tag(ui, repo, name1, *othernames, **opts):
+    """add one or more tags for the current or given revision
 
     Name a particular revision using <name>.
 
     Tags are used to name particular revisions of the repository and are
-    very useful to compare different revision, to go back to significant
+    very useful to compare different revisions, to go back to significant
     earlier versions or to mark branch points as releases, etc.
 
     If no revision is given, the parent of the working directory is used,
@@ -2552,43 +2552,56 @@
 
     See 'hg help dates' for a list of formats valid for -d/--date.
     """
-    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"))
+
+    def abort_nonempty(names, singular_msg, plural_msg):
+        '''if names is a non-empty list, abort with a message listing the
+        offending name or names'''
+        if not names:
+            return
+        if len(names) == 1:
+            raise util.Abort(singular_msg % ("'" + names[0] + "'"))
+        raise util.Abort(plural_msg % ("'" + "', '".join(names) + "'"))
+
+    rev_ = None
+    names = (name1,) + othernames
+
+    if othernames and len(names) != len({}.fromkeys(names)):
+        raise util.Abort(_('tag names must be unique'))
+    abort_nonempty([n for n in names if n in ['tip', '.', 'null']],
+                   _('the name %s is reserved'),
+                   _('the names %s are reserved'))
+
     if opts['rev'] and opts['remove']:
         raise util.Abort(_("--rev and --remove are incompatible"))
     if opts['rev']:
         rev_ = opts['rev']
     message = opts['message']
+    tag_word = (len(names) > 1 and _('tags')) or _('tag')
     if opts['remove']:
-        tagtype = repo.tagtype(name)
-
-        if not tagtype:
-            raise util.Abort(_('tag %s does not exist') % name)
-        if opts['local'] and tagtype == 'global':
-           raise util.Abort(_('%s tag is global') % name)
-        if not opts['local'] and tagtype == 'local':
-           raise util.Abort(_('%s tag is local') % name)
-
+        abort_nonempty([n for n in names if not repo.tagtype(n)],
+                       _('tag %s does not exist'),
+                       _('tags %s do not exist'))
+        expectedtype = opts['local'] and 'local' or 'global'
+        abort_nonempty([n for n in names if repo.tagtype(n) != expectedtype],
+                       _('tag %s is not a ' + expectedtype + ' tag'),
+                       _('tags %s are not ' + expectedtype + ' tags'))
         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 %s %s') % (tag_word, ", ".join(names))
+    elif not opts['force']:
+        abort_nonempty([n for n in names if n in repo.tags()],
+                       _('tag %s already exists (use -f to force)'),
+                       _('tags %s already exist (use -f to force)'))
     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 %s %s for changeset %s') %
+                   (tag_word, ", ".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
@@ -3118,7 +3131,7 @@
           # -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 -r f857eac30cd5 -r fa824ac29642 mercurial/localrepo.py
--- a/mercurial/localrepo.py	Thu Feb 21 20:56:06 2008 +0100
+++ b/mercurial/localrepo.py	Fri Feb 22 23:06:46 2008 -0800
@@ -124,21 +124,25 @@
 
     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):
             fp.seek(0, 2)
             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()
 
         prevtags = ''
@@ -151,8 +155,9 @@
                 prevtags = fp.read()
 
             # local tags are stored in the current charset
-            writetag(fp, name, None, prevtags)
-            self.hook('tag', node=hex(node), tag=name, local=local)
+            writetags(fp, names, None, prevtags)
+            for name in names:
+                self.hook('tag', node=hex(node), tag=name, local=local)
             return
 
         if use_dirstate:
@@ -172,7 +177,7 @@
                 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'])
@@ -180,20 +185,21 @@
         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
+        if local is True, the tags are stored in a per-repository file.
+        otherwise, they are stored in the .hgtags file, and a new
         changeset is committed with the change.
 
         keyword arguments:
 
-        local: whether to store tag in non-version-controlled file
+        local: whether to store tags in non-version-controlled file
         (default False)
 
         message: commit message to use if committing
@@ -208,8 +214,7 @@
                 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 -r f857eac30cd5 -r fa824ac29642 tests/test-globalopts.out
--- a/tests/test-globalopts.out	Thu Feb 21 20:56:06 2008 +0100
+++ b/tests/test-globalopts.out	Fri Feb 22 23:06:46 2008 -0800
@@ -188,7 +188,7 @@
  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
@@ -241,7 +241,7 @@
  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 -r f857eac30cd5 -r fa824ac29642 tests/test-help.out
--- a/tests/test-help.out	Thu Feb 21 20:56:06 2008 +0100
+++ b/tests/test-help.out	Fri Feb 22 23:06:46 2008 -0800
@@ -80,7 +80,7 @@
  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
@@ -129,7 +129,7 @@
  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 -r f857eac30cd5 -r fa824ac29642 tests/test-tag
--- a/tests/test-tag	Thu Feb 21 20:56:06 2008 +0100
+++ b/tests/test-tag	Fri Feb 22 23:06:46 2008 -0800
@@ -10,11 +10,21 @@
 
 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 -r f857eac30cd5 -r fa824ac29642 tests/test-tag.out
--- a/tests/test-tag.out	Thu Feb 21 20:56:06 2008 +0100
+++ b/tests/test-tag.out	Fri Feb 22 23:06:46 2008 -0800
@@ -18,12 +18,26 @@
 
 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: tag names must be unique
 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: tag 'bleah' already exists (use -f to force)
+failed
+abort: tag 'bleah' already exists (use -f to force)
+failed
+abort: tag 'blecch' does not exist
+failed
+abort: 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 -r f857eac30cd5 -r fa824ac29642 tests/test-tags.out
--- a/tests/test-tags.out	Thu Feb 21 20:56:06 2008 +0100
+++ b/tests/test-tags.out	Fri Feb 22 23:06:46 2008 -0800
@@ -50,7 +50,7 @@
 
 tip                                5:57e1983b4a60
 % remove nonexistent tag
-abort: tag foobar does not exist
+abort: tag 'foobar' does not exist
 changeset:   5:57e1983b4a60
 tag:         tip
 user:        test
@@ -62,7 +62,7 @@
 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: tag 'bar' already exists (use -f to force)
 tip                                6:b5ff9d142648
 bar                                0:b409d9da318e
 adding foo
@@ -72,8 +72,8 @@
 tip                                4:40af5d225513
 bar                                2:72b852876a42
 adding foo
-abort: localtag tag is local
-abort: globaltag tag is global
+abort: tag 'localtag' is not a global tag
+abort: tag 'globaltag' is not a local tag
 tip                                1:a0b6fe111088
 localtag                           0:bbd179dfa0a7 local
 globaltag                          0:bbd179dfa0a7




More information about the Mercurial-devel mailing list