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