Rev 2222: Add tag command and basic implementation in file:///home/mbp/bzr/Work/tags/

Martin Pool mbp at sourcefrog.net
Tue Jan 16 13:21:42 GMT 2007


------------------------------------------------------------
revno: 2222
revision-id: mbp at sourcefrog.net-20070116132138-vhziz9hpfc8za0uj
parent: mbp at sourcefrog.net-20070114085612-q90lx2g0firzu7z9
committer: Martin Pool <mbp at sourcefrog.net>
branch nick: tags
timestamp: Wed 2007-01-17 00:21:38 +1100
message:
  Add tag command and basic implementation
added:
  bzrlib/tests/blackbox/test_tags.py test_tags.py-20070116132048-5h4qak2cm22jlb9e-1
modified:
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
  bzrlib/tests/blackbox/__init__.py __init__.py-20051128053524-eba30d8255e08dc3
  bzrlib/tests/repository_implementations/test_tags.py test_tags.py-20070114073919-azsbo9l26ph1rr09-1
  bzrlib/tests/test_repository.py test_repository.py-20060131075918-65c555b881612f4d
=== added file 'bzrlib/tests/blackbox/test_tags.py'
--- a/bzrlib/tests/blackbox/test_tags.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/blackbox/test_tags.py	2007-01-16 13:21:38 +0000
@@ -0,0 +1,51 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests for commands related to tags"""
+
+from bzrlib.branch import Branch
+from bzrlib.bzrdir import BzrDir
+from bzrlib.tests import TestCaseWithTransport
+from bzrlib.repository import RepositoryFormatKnit2
+
+
+class TestTagging(TestCaseWithTransport):
+
+    # as of 0.14, the default format doesn't do tags so we need to use a
+    # specific format
+    
+    def make_branch_and_tree(self, relpath):
+        control = BzrDir.create(relpath)
+        repo = RepositoryFormatKnit2().initialize(control)
+        control.create_branch()
+        return control.create_workingtree()
+
+    def test_tag_command_help(self):
+        out, err = self.run_bzr_captured(['help', 'tag'])
+        self.assertContainsRe(out, 'Create a tag')
+
+    def test_tag_current_rev(self):
+        ## b = self.make_branch('branch')
+        ## b.repository.tag
+        t = self.make_branch_and_tree('branch')
+        t.commit(allow_pointless=True, message='initial commit',
+            rev_id='first-revid')
+        # make a tag through the command line
+        out, err = self.run_bzr('tag', '-d', 'branch', 'NEWTAG')
+        self.assertContainsRe(out, 'created tag NEWTAG')
+        # tag should be observable through the api
+        repo = t.branch.repository
+        self.assertEquals(repo.get_tag_dict(), dict(NEWTAG='first-revid'))

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-01-03 02:06:33 +0000
+++ b/bzrlib/builtins.py	2007-01-16 13:21:38 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2004, 2005, 2006 Canonical Ltd
+# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -2994,6 +2994,27 @@
         server.serve()
 
 
+class cmd_tag(Command):
+    """Create a tag naming a revision"""
+
+    takes_args = ['tag_name']
+    takes_options = [
+        Option('directory',
+            help='branch or repository to tag',
+            short_name='d',
+            type=unicode,
+            ),
+        ]
+
+    def run(self, tag_name, directory='.'):
+        branch, relpath = Branch.open_containing(directory)
+        revision_id = branch.last_revision()
+        branch.repository.make_tag(tag_name, revision_id)
+        self.outf.write('created tag %s' % tag_name)
+
+
+
+
 # command-line interpretation helper for merge-related commands
 def _merge_helper(other_revision, base_revision,
                   check_clean=True, ignore_zero=False,

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2006-12-21 20:07:34 +0000
+++ b/bzrlib/errors.py	2007-01-16 13:21:38 +0000
@@ -1602,3 +1602,11 @@
     def __init__(self, name):
         BzrError.__init__(self)
         self.name = name
+
+
+class NoSuchTag(BzrError):
+
+    _fmt = "No such tag: %(tag_name)s"
+
+    def __init__(self, tag_name):
+        self.tag_name = tag_name

=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py	2007-01-14 08:56:12 +0000
+++ b/bzrlib/repository.py	2007-01-16 13:21:38 +0000
@@ -1090,10 +1090,15 @@
 class KnitRepository2(KnitRepository):
     """Experimental enhanced knit format"""
 
+    # corresponds to RepositoryFormatKnit2
+    
+    # TODO: within a lock scope, we could keep the tags in memory...
+
     def __init__(self, _format, a_bzrdir, control_files, _revision_store,
                  control_store, text_store):
         KnitRepository.__init__(self, _format, a_bzrdir, control_files,
                               _revision_store, control_store, text_store)
+        self._transport = control_files._transport
         self._serializer = xml6.serializer_v6
 
     def deserialise_inventory(self, revision_id, xml):
@@ -1133,8 +1138,37 @@
         return RootCommitBuilder(self, parents, config, timestamp, timezone,
                                  committer, revprops, revision_id)
 
-    def get_tags(self):
-        return None
+    @needs_read_lock
+    def get_tag_dict(self):
+        tag_content = self.control_files.get_utf8('tags').read()
+        return self._format._deserialize_tag_dict(tag_content)
+
+    @needs_write_lock
+    def _set_tag_dict(self, new_dict):
+        """Replace all tag definitions
+
+        :param new_dict: Dictionary from tag name to target.
+        """
+        self.control_files.put_utf8('tags', self._format._serialize_tag_dict(new_dict))
+
+    @needs_write_lock
+    def make_tag(self, tag_name, tag_target):
+        """Add a tag definition to the repository.
+
+        Behaviour if the tag is already present is not defined (yet).
+        """
+        # all done with a write lock held, so this looks atomic
+        td = self.get_tag_dict()
+        td[tag_name] = tag_target
+        self._set_tag_dict(td)
+
+    def lookup_tag(self, tag_name):
+        """Return the referent string of a tag"""
+        td = self.get_tag_dict()
+        try:
+            return td[tag_name]
+        except KeyError:
+            raise errors.NoSuchTag(tag_name)
 
 
 class RepositoryFormat(object):
@@ -1222,7 +1256,7 @@
         from bzrlib.store.revision.text import TextRevisionStore
         dir_mode = control_files._dir_mode
         file_mode = control_files._file_mode
-        text_store =TextStore(transport.clone(name),
+        text_store = TextStore(transport.clone(name),
                               prefixed=prefixed,
                               compressed=compressed,
                               dir_mode=dir_mode,
@@ -1801,11 +1835,31 @@
                                control_files=control_files,
                                _revision_store=_revision_store,
                                control_store=control_store,
-                               text_store=text_store)
+                                text_store=text_store)
+
+    def initialize(self, a_bzrdir, shared=False):
+        repo = super(RepositoryFormatKnit2, self).initialize(a_bzrdir, shared)
+        repo._transport.put_bytes_non_atomic('tags', '')
+        return repo
 
     def supports_tags(self):
         return True
 
+    def _serialize_tag_dict(self, tag_dict):
+        s = []
+        for tag, target in sorted(tag_dict.items()):
+            # TODO: check that tag names and targets are acceptable
+            s.append(tag + '\t' + target + '\n')
+        return ''.join(s)
+
+    def _deserialize_tag_dict(self, tag_content):
+        """Convert the tag file into a dictionary of tags"""
+        d = {}
+        for l in tag_content.splitlines():
+            tag, target = l.split('\t', 1)
+            d[tag] = target
+        return d
+
 
 
 # formats which have no format string are not discoverable

=== modified file 'bzrlib/tests/blackbox/__init__.py'
--- a/bzrlib/tests/blackbox/__init__.py	2006-11-18 22:38:16 +0000
+++ b/bzrlib/tests/blackbox/__init__.py	2007-01-16 13:21:38 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005, 2006 Canonical Ltd
+# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -87,6 +87,7 @@
                      'bzrlib.tests.blackbox.test_shared_repository',
                      'bzrlib.tests.blackbox.test_sign_my_commits',
                      'bzrlib.tests.blackbox.test_status',
+                     'bzrlib.tests.blackbox.test_tags',
                      'bzrlib.tests.blackbox.test_testament',
                      'bzrlib.tests.blackbox.test_too_much',
                      'bzrlib.tests.blackbox.test_uncommit',

=== modified file 'bzrlib/tests/repository_implementations/test_tags.py'
--- a/bzrlib/tests/repository_implementations/test_tags.py	2007-01-14 08:56:12 +0000
+++ b/bzrlib/tests/repository_implementations/test_tags.py	2007-01-16 13:21:38 +0000
@@ -23,9 +23,11 @@
 import bzrlib
 from bzrlib import bzrdir, errors, repository
 from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
+from bzrlib.repository import Repository
 from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
 from bzrlib.trace import mutter
 from bzrlib.workingtree import WorkingTree
+
 from bzrlib.tests.repository_implementations.test_repository \
         import TestCaseWithRepository
 
@@ -44,6 +46,33 @@
             raise TestSkipped("format %s doesn't support tags" % fmt)
         TestCaseWithRepository.setUp(self)
 
-    def test_foo_tags(self):
-        repo = self.make_repository('repo')
-        tags = repo.get_tags()
+    def test_tags_initially_empty(self):
+        repo = self.make_repository('repo')
+        tags = repo.get_tag_dict()
+        self.assertEqual(tags, {})
+
+    def test_set_get_tags(self):
+        # add two tags, 
+        repo = self.make_repository('repo')
+        td = dict(stable='stable-revid', boring='boring-revid')
+        repo._set_tag_dict(td)
+        # then reopen the repo and see they're still there
+        repo = Repository.open('repo')
+        self.assertEqual(repo.get_tag_dict(), td)
+
+    def test_make_and_lookup_tag(self):
+        repo = self.make_repository('repo')
+        repo.make_tag('tag-name', 'target-revid-1')
+        result = repo.lookup_tag('tag-name')
+        self.assertEqual(result, 'target-revid-1')
+
+    def test_no_such_tag(self):
+        repo = self.make_repository('repo')
+        try:
+            repo.lookup_tag('bosko')
+        except errors.NoSuchTag, e:
+            self.assertEquals(e.tag_name, 'bosko')
+            self.assertEquals(str(e), 'No such tag: bosko')
+        else:
+            self.fail("didn't get expected exception")
+

=== modified file 'bzrlib/tests/test_repository.py'
--- a/bzrlib/tests/test_repository.py	2006-10-12 14:29:32 +0000
+++ b/bzrlib/tests/test_repository.py	2007-01-16 13:21:38 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 Canonical Ltd
+# Copyright (C) 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -321,7 +321,6 @@
         self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
         self.check_knits(t)
 
-
 class InterString(repository.InterRepository):
     """An inter-repository optimised code path for strings.
 
@@ -458,3 +457,15 @@
         tree.commit("Another dull commit", rev_id='dull2')
         revision_tree = tree.branch.repository.revision_tree('dull2')
         self.assertEqual('dull', revision_tree.inventory.root.revision)
+
+    def test_tag_serialization(self):
+        """Test the precise representation of tag dicts."""
+        # Don't change this after we commit to this format, as it checks 
+        # that the format is stable and compatible across releases
+        format = repository.RepositoryFormatKnit2()
+        td = dict(stable='stable-revid', boring='boring-revid')
+        packed = format._serialize_tag_dict(td)
+        expected = 'boring\tboring-revid\nstable\tstable-revid\n'
+        self.assertEqualDiff(packed, expected)
+        self.assertEqual(format._deserialize_tag_dict(packed), td)
+




More information about the bazaar-commits mailing list