~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tag.py

  • Committer: Andrew Bennetts
  • Date: 2007-03-28 07:08:42 UTC
  • mfrom: (2380 +trunk)
  • mto: (2018.5.146 hpss)
  • mto: This revision was merged to the branch mainline in revision 2414.
  • Revision ID: andrew.bennetts@canonical.com-20070328070842-r843houy668oxb9o
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tag strategies.
 
18
 
 
19
These are contained within a branch and normally constructed 
 
20
when the branch is opened.  Clients should typically do 
 
21
 
 
22
  Branch.tags.add('name', 'value')
 
23
"""
 
24
 
 
25
# NOTE: I was going to call this tags.py, but vim seems to think all files
 
26
# called tags* are ctags files... mbp 20070220.
 
27
 
 
28
 
 
29
from warnings import warn
 
30
 
 
31
from bzrlib import (
 
32
    errors,
 
33
    trace,
 
34
    )
 
35
from bzrlib.util import bencode
 
36
 
 
37
 
 
38
class _Tags(object):
 
39
 
 
40
    def __init__(self, branch):
 
41
        self.branch = branch
 
42
 
 
43
    def has_tag(self, tag_name):
 
44
        return self.get_tag_dict().has_key(tag_name)
 
45
 
 
46
 
 
47
class DisabledTags(_Tags):
 
48
    """Tag storage that refuses to store anything.
 
49
 
 
50
    This is used by older formats that can't store tags.
 
51
    """
 
52
 
 
53
    def _not_supported(self, *a, **k):
 
54
        raise errors.TagsNotSupported(self.branch)
 
55
 
 
56
    def supports_tags(self):
 
57
        return False
 
58
 
 
59
    set_tag = _not_supported
 
60
    get_tag_dict = _not_supported
 
61
    _set_tag_dict = _not_supported
 
62
    lookup_tag = _not_supported
 
63
    delete_tag = _not_supported
 
64
 
 
65
    def merge_to(self, to_tags):
 
66
        # we never have anything to copy
 
67
        pass
 
68
 
 
69
 
 
70
class BasicTags(_Tags):
 
71
    """Tag storage in an unversioned branch control file.
 
72
    """
 
73
 
 
74
    def supports_tags(self):
 
75
        return True
 
76
 
 
77
    def set_tag(self, tag_name, tag_target):
 
78
        """Add a tag definition to the branch.
 
79
 
 
80
        Behaviour if the tag is already present is not defined (yet).
 
81
        """
 
82
        # all done with a write lock held, so this looks atomic
 
83
        self.branch.lock_write()
 
84
        try:
 
85
            td = self.get_tag_dict()
 
86
            td[tag_name] = tag_target
 
87
            self._set_tag_dict(td)
 
88
        finally:
 
89
            self.branch.unlock()
 
90
 
 
91
    def lookup_tag(self, tag_name):
 
92
        """Return the referent string of a tag"""
 
93
        td = self.get_tag_dict()
 
94
        try:
 
95
            return td[tag_name]
 
96
        except KeyError:
 
97
            raise errors.NoSuchTag(tag_name)
 
98
 
 
99
    def get_tag_dict(self):
 
100
        self.branch.lock_read()
 
101
        try:
 
102
            try:
 
103
                tag_content = self.branch._transport.get_bytes('tags')
 
104
            except errors.NoSuchFile, e:
 
105
                # ugly, but only abentley should see this :)
 
106
                trace.warning('No branch/tags file in %s.  '
 
107
                     'This branch was probably created by bzr 0.15pre.  '
 
108
                     'Create an empty file to silence this message.'
 
109
                     % (self.branch, ))
 
110
                return {}
 
111
            return self._deserialize_tag_dict(tag_content)
 
112
        finally:
 
113
            self.branch.unlock()
 
114
 
 
115
    def delete_tag(self, tag_name):
 
116
        """Delete a tag definition.
 
117
        """
 
118
        self.branch.lock_write()
 
119
        try:
 
120
            d = self.get_tag_dict()
 
121
            try:
 
122
                del d[tag_name]
 
123
            except KeyError:
 
124
                raise errors.NoSuchTag(tag_name)
 
125
            self._set_tag_dict(d)
 
126
        finally:
 
127
            self.branch.unlock()
 
128
 
 
129
    def _set_tag_dict(self, new_dict):
 
130
        """Replace all tag definitions
 
131
 
 
132
        :param new_dict: Dictionary from tag name to target.
 
133
        """
 
134
        self.branch.lock_read()
 
135
        try:
 
136
            self.branch._transport.put_bytes('tags',
 
137
                self._serialize_tag_dict(new_dict))
 
138
        finally:
 
139
            self.branch.unlock()
 
140
 
 
141
    def _serialize_tag_dict(self, tag_dict):
 
142
        td = dict((k.encode('utf-8'), v)
 
143
                for k,v in tag_dict.items())
 
144
        return bencode.bencode(td)
 
145
 
 
146
    def _deserialize_tag_dict(self, tag_content):
 
147
        """Convert the tag file into a dictionary of tags"""
 
148
        # was a special case to make initialization easy, an empty definition
 
149
        # is an empty dictionary
 
150
        if tag_content == '':
 
151
            return {}
 
152
        try:
 
153
            r = {}
 
154
            for k, v in bencode.bdecode(tag_content).items():
 
155
                r[k.decode('utf-8')] = v
 
156
            return r
 
157
        except ValueError, e:
 
158
            raise ValueError("failed to deserialize tag dictionary %r: %s"
 
159
                % (tag_content, e))
 
160
 
 
161
    def merge_to(self, to_tags):
 
162
        """Copy tags between repositories if necessary and possible.
 
163
        
 
164
        This method has common command-line behaviour about handling 
 
165
        error cases.
 
166
 
 
167
        All new definitions are copied across, except that tags that already
 
168
        exist keep their existing definitions.
 
169
 
 
170
        :param to_tags: Branch to receive these tags
 
171
        :param just_warn: If the destination doesn't support tags and the 
 
172
            source does have tags, just give a warning.  Otherwise, raise
 
173
            TagsNotSupported (default).
 
174
 
 
175
        :returns: A list of tags that conflicted, each of which is 
 
176
            (tagname, source_target, dest_target).
 
177
        """
 
178
        if self.branch == to_tags.branch:
 
179
            return
 
180
        if not self.supports_tags():
 
181
            # obviously nothing to copy
 
182
            return
 
183
        source_dict = self.get_tag_dict()
 
184
        if not source_dict:
 
185
            # no tags in the source, and we don't want to clobber anything
 
186
            # that's in the destination
 
187
            return
 
188
        to_tags.branch.lock_write()
 
189
        try:
 
190
            dest_dict = to_tags.get_tag_dict()
 
191
            result, conflicts = self._reconcile_tags(source_dict, dest_dict)
 
192
            if result != dest_dict:
 
193
                to_tags._set_tag_dict(result)
 
194
        finally:
 
195
            to_tags.branch.unlock()
 
196
        return conflicts
 
197
 
 
198
    def _reconcile_tags(self, source_dict, dest_dict):
 
199
        """Do a two-way merge of two tag dictionaries.
 
200
 
 
201
        only in source => source value
 
202
        only in destination => destination value
 
203
        same definitions => that
 
204
        different definitions => keep destination value, give a warning
 
205
 
 
206
        :returns: (result_dict,
 
207
            [(conflicting_tag, source_target, dest_target)])
 
208
        """
 
209
        conflicts = []
 
210
        result = dict(dest_dict) # copy
 
211
        for name, target in source_dict.items():
 
212
            if name not in result:
 
213
                result[name] = target
 
214
            elif result[name] == target:
 
215
                pass
 
216
            else:
 
217
                conflicts.append((name, target, result[name]))
 
218
        return result, conflicts
 
219
 
 
220
 
 
221
def _merge_tags_if_possible(from_branch, to_branch):
 
222
    from_branch.tags.merge_to(to_branch.tags)