Rev 2223: Add tag: revision namespace. in file:///home/mbp/bzr/Work/tags/

Martin Pool mbp at sourcefrog.net
Fri Jan 19 02:46:52 GMT 2007


------------------------------------------------------------
revno: 2223
revision-id: mbp at sourcefrog.net-20070119024650-o12ekgnjski2v3ed
parent: mbp at sourcefrog.net-20070116132138-vhziz9hpfc8za0uj
committer: Martin Pool <mbp at sourcefrog.net>
branch nick: tags
timestamp: Fri 2007-01-19 13:46:50 +1100
message:
  Add tag: revision namespace.
  
  Change make_tag to set_tag
  
  Add Branch.lookup_tag (defers to Branch.lookup_tag) for simplicity of callers,
  and possibly to help formats where it's better to look up by branch.
  
  Factor out RevisionInfo.from_revision_id
  
  Separate tag storage into a _TagStore strategy within the Repository, 
  so they can be reused across formats.
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
  bzrlib/revisionspec.py         revisionspec.py-20050907152633-17567659fd5c0ddb
  bzrlib/tests/repository_implementations/test_tags.py test_tags.py-20070114073919-azsbo9l26ph1rr09-1
  bzrlib/tests/test_repository.py test_repository.py-20060131075918-65c555b881612f4d
  bzrlib/tests/test_revisionnamespaces.py testrevisionnamespaces.py-20050711050225-8b4af89e6b1efe84
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2006-12-07 16:21:39 +0000
+++ b/bzrlib/branch.py	2007-01-19 02:46:50 +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

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-01-16 13:21:38 +0000
+++ b/bzrlib/builtins.py	2007-01-19 02:46:50 +0000
@@ -3009,7 +3009,7 @@
     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)
+        branch.repository.set_tag(tag_name, revision_id)
         self.outf.write('created tag %s' % tag_name)
 
 

=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py	2007-01-16 13:21:38 +0000
+++ b/bzrlib/repository.py	2007-01-19 02:46:50 +0000
@@ -71,6 +71,91 @@
 _deprecation_warning_done = False
 
 
+######################################################################
+# tag storage
+
+
+class _TagStore(object):
+    def __init__(self, repository):
+        self.repository = repository
+
+class _DisabledTagStore(_TagStore):
+    """Tag storage that refuses to store anything.
+
+    This is used by older formats that can't store tags.
+    """
+
+    def _not_supported(self, *a, **k):
+        raise errors.TagsNotSupported(self.repository)
+
+    set_tag = _not_supported
+
+
+class _BasicTagStore(_TagStore):
+    """Tag storage in an unversioned repository control file.
+    """
+
+    def set_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
+        self.repository.lock_write()
+        try:
+            td = self.get_tag_dict()
+            td[tag_name] = tag_target
+            self._set_tag_dict(td)
+        finally:
+            self.repository.unlock()
+
+    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)
+
+    def get_tag_dict(self):
+        self.repository.lock_read()
+        try:
+            tag_content = self.repository.control_files.get_utf8('tags').read()
+            return self._deserialize_tag_dict(tag_content)
+        finally:
+            self.repository.unlock()
+
+    def _set_tag_dict(self, new_dict):
+        """Replace all tag definitions
+
+        :param new_dict: Dictionary from tag name to target.
+        """
+        self.repository.lock_read()
+        try:
+            self.repository.control_files.put_utf8('tags',
+                self._serialize_tag_dict(new_dict))
+        finally:
+            self.repository.unlock()
+
+    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
+
+
+######################################################################
+# Repositories
+
 class Repository(object):
     """Repository holding history for one or more branches.
 
@@ -83,6 +168,9 @@
     remote) disk.
     """
 
+    # override this to set the strategy for storing tags
+    _tag_store_class = _DisabledTagStore
+
     _file_ids_altered_regex = lazy_regex.lazy_compile(
         r'file_id="(?P<file_id>[^"]+)"'
         r'.*revision="(?P<revision_id>[^"]+)"'
@@ -222,6 +310,7 @@
         # on whether escaping is required.
         self._warn_if_deprecated()
         self._serializer = xml5.serializer_v5
+        self._tag_store = self._tag_store_class(self)
 
     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, 
@@ -775,6 +864,15 @@
                 except UnicodeEncodeError:
                     raise errors.NonAsciiRevisionId(method, self)
 
+    def set_tag(self, tag_name, tag_target):
+        self._tag_store.set_tag(tag_name, tag_target)
+
+    def lookup_tag(self, tag_name):
+        return self._tag_store.lookup_tag(tag_name)
+
+    def get_tag_dict(self):
+        return self._tag_store.get_tag_dict()
+
 
 class AllInOneRepository(Repository):
     """Legacy support - the repository behaviour for all-in-one branches."""
@@ -1093,6 +1191,8 @@
     # corresponds to RepositoryFormatKnit2
     
     # TODO: within a lock scope, we could keep the tags in memory...
+    
+    _tag_store_class = _BasicTagStore
 
     def __init__(self, _format, a_bzrdir, control_files, _revision_store,
                  control_store, text_store):
@@ -1138,38 +1238,9 @@
         return RootCommitBuilder(self, parents, config, timestamp, timezone,
                                  committer, revprops, revision_id)
 
-    @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)
-
+
+#####################################################################
+# Repository Formats
 
 class RepositoryFormat(object):
     """A repository format.
@@ -1845,21 +1916,6 @@
     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/revisionspec.py'
--- a/bzrlib/revisionspec.py	2006-11-08 16:29:27 +0000
+++ b/bzrlib/revisionspec.py	2007-01-19 02:46:50 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005 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
@@ -93,6 +93,18 @@
         return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
             self.revno, self.rev_id, self.branch)
 
+    @staticmethod
+    def from_revision_id(branch, revision_id, revs):
+        """Construct a RevisionInfo given just the id.
+
+        Use this if you don't know or care what the revno is.
+        """
+        try:
+            revno = revs.index(revision_id) + 1
+        except ValueError:
+            revno = None
+        return RevisionInfo(branch, revno, revision_id)
+
 
 # classes in this list should have a "prefix" attribute, against which
 # string specs are matched
@@ -365,11 +377,7 @@
     prefix = 'revid:'
 
     def _match_on(self, branch, revs):
-        try:
-            revno = revs.index(self.spec) + 1
-        except ValueError:
-            revno = None
-        return RevisionInfo(branch, revno, self.spec)
+        return RevisionInfo.from_revision_id(branch, self.spec, revs)
 
 SPEC_TYPES.append(RevisionSpec_revid)
 
@@ -463,16 +471,21 @@
 
 
 class RevisionSpec_tag(RevisionSpec):
-    """To be implemented."""
-
-    help_txt = """To be implemented."""
+    """Select a revision identified by tag name"""
+
+    help_txt = """Selects a revision identified by a tag name.
+
+    Tags are stored in the repository and created by the 'tag'
+    command.
+    """
 
     prefix = 'tag:'
 
     def _match_on(self, branch, revs):
-        raise errors.InvalidRevisionSpec(self.user_spec, branch,
-                                         'tag: namespace registered,'
-                                         ' but not implemented')
+        # Can raise tags not supported, NoSuchTag, etc
+        return RevisionInfo.from_revision_id(branch,
+            branch.repository.lookup_tag(self.spec),
+            revs)
 
 SPEC_TYPES.append(RevisionSpec_tag)
 

=== modified file 'bzrlib/tests/repository_implementations/test_tags.py'
--- a/bzrlib/tests/repository_implementations/test_tags.py	2007-01-16 13:21:38 +0000
+++ b/bzrlib/tests/repository_implementations/test_tags.py	2007-01-19 02:46:50 +0000
@@ -51,18 +51,17 @@
         tags = repo.get_tag_dict()
         self.assertEqual(tags, {})
 
-    def test_set_get_tags(self):
-        # add two tags, 
+    def test_make_and_lookup_tag(self):
         repo = self.make_repository('repo')
-        td = dict(stable='stable-revid', boring='boring-revid')
-        repo._set_tag_dict(td)
+        repo.set_tag('tag-name', 'target-revid-1')
+        repo.set_tag('other-name', 'target-revid-2')
         # 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')
+        self.assertEqual(repo.get_tag_dict(), 
+            {'tag-name': 'target-revid-1',
+             'other-name': 'target-revid-2',
+            })
+        # read one at a time
         result = repo.lookup_tag('tag-name')
         self.assertEqual(result, 'target-revid-1')
 

=== modified file 'bzrlib/tests/test_repository.py'
--- a/bzrlib/tests/test_repository.py	2007-01-16 13:21:38 +0000
+++ b/bzrlib/tests/test_repository.py	2007-01-19 02:46:50 +0000
@@ -462,10 +462,10 @@
         """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()
+        store = repository._BasicTagStore(repository=None)
         td = dict(stable='stable-revid', boring='boring-revid')
-        packed = format._serialize_tag_dict(td)
+        packed = store._serialize_tag_dict(td)
         expected = 'boring\tboring-revid\nstable\tstable-revid\n'
         self.assertEqualDiff(packed, expected)
-        self.assertEqual(format._deserialize_tag_dict(packed), td)
+        self.assertEqual(store._deserialize_tag_dict(packed), td)
 

=== modified file 'bzrlib/tests/test_revisionnamespaces.py'
--- a/bzrlib/tests/test_revisionnamespaces.py	2006-10-16 10:03:21 +0000
+++ b/bzrlib/tests/test_revisionnamespaces.py	2007-01-19 02:46:50 +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
@@ -19,11 +19,17 @@
 import time
 
 from bzrlib import (
+    bzrdir,
     errors,
+    repository,
     )
 from bzrlib.builtins import merge
 from bzrlib.tests import TestCase, TestCaseWithTransport
-from bzrlib.revisionspec import RevisionSpec, RevisionSpec_revno
+from bzrlib.revisionspec import (
+    RevisionSpec,
+    RevisionSpec_revno,
+    RevisionSpec_tag,
+    )
 
 
 def spec_in_history(spec, branch):
@@ -335,9 +341,28 @@
 
 class TestRevisionSpec_tag(TestRevisionSpec):
     
-    def test_invalid(self):
-        self.assertInvalid('tag:foo', extra='\ntag: namespace registered,'
-                                            ' but not implemented')
+    def make_branch_and_tree(self, relpath):
+        # override format as the default one may not support tags
+        control = bzrdir.BzrDir.create(relpath)
+        repo = repository.RepositoryFormatKnit2().initialize(control)
+        control.create_branch()
+        return control.create_workingtree()
+
+    def test_from_string_tag(self):
+        spec = RevisionSpec.from_string('tag:bzr-0.14')
+        self.assertIsInstance(spec, RevisionSpec_tag)
+        self.assertEqual(spec.spec, 'bzr-0.14')
+
+    def test_lookup_tag(self):
+        self.tree.branch.repository.set_tag('bzr-0.14', 'r1')
+        self.assertInHistoryIs(1, 'r1', 'tag:bzr-0.14')
+
+    def test_failed_lookup(self):
+        # tags that don't exist give a specific message: arguably we should
+        # just give InvalidRevisionSpec but I think this is more helpful
+        self.assertRaises(errors.NoSuchTag,
+            self.get_in_history,
+            'tag:some-random-tag')
 
 
 class TestRevisionSpec_date(TestRevisionSpec):




More information about the bazaar-commits mailing list