~bzr-pqm/bzr/bzr.dev

2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
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
2220.2.20 by Martin Pool
Tag methods now available through Branch.tags.add_tag, etc
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.
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
27
2220.2.14 by mbp at sourcefrog
cleanup
28
2220.2.28 by Martin Pool
Integrate tags with Branch6:
29
from warnings import warn
30
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
31
from bzrlib import (
32
    errors,
2220.2.27 by Martin Pool
Start adding tags to Branch6
33
    trace,
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
34
    )
2220.2.15 by mbp at sourcefrog
Store tag dictionary in bencode and accomodate non-ascii tags
35
from bzrlib.util import bencode
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
36
37
2220.2.20 by Martin Pool
Tag methods now available through Branch.tags.add_tag, etc
38
class _Tags(object):
2220.2.28 by Martin Pool
Integrate tags with Branch6:
39
2220.2.14 by mbp at sourcefrog
cleanup
40
    def __init__(self, branch):
41
        self.branch = branch
42
2220.2.42 by Martin Pool
Tag command refuses to replace existing tags unless you force it.
43
    def has_tag(self, tag_name):
44
        return self.get_tag_dict().has_key(tag_name)
45
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
46
2220.2.20 by Martin Pool
Tag methods now available through Branch.tags.add_tag, etc
47
class DisabledTags(_Tags):
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
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):
2220.2.14 by mbp at sourcefrog
cleanup
54
        raise errors.TagsNotSupported(self.branch)
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the 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
2220.2.21 by Martin Pool
Add tag --delete command and implementation
63
    delete_tag = _not_supported
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
64
2810.1.1 by Martin Pool
merge push|pull --overwrite and tweak
65
    def merge_to(self, to_tags, overwrite=False):
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
66
        # we never have anything to copy
67
        pass
68
2831.8.1 by James Westby
Fix log against smart server branches that don't support tags. (#140615)
69
    def get_reverse_tag_dict(self):
70
        # There aren't any tags, so the reverse mapping is empty.
71
        return {}
72
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
73
2220.2.20 by Martin Pool
Tag methods now available through Branch.tags.add_tag, etc
74
class BasicTags(_Tags):
2220.2.14 by mbp at sourcefrog
cleanup
75
    """Tag storage in an unversioned branch control file.
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
76
    """
77
78
    def supports_tags(self):
79
        return True
80
81
    def set_tag(self, tag_name, tag_target):
2220.2.14 by mbp at sourcefrog
cleanup
82
        """Add a tag definition to the branch.
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
83
84
        Behaviour if the tag is already present is not defined (yet).
85
        """
86
        # all done with a write lock held, so this looks atomic
2220.2.14 by mbp at sourcefrog
cleanup
87
        self.branch.lock_write()
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
88
        try:
2805.5.2 by Martin Pool
Setting and deleting tags should also update the master branch, if any.
89
            master = self.branch.get_master_branch()
90
            if master is not None:
91
                master.tags.set_tag(tag_name, tag_target)
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
92
            td = self.get_tag_dict()
93
            td[tag_name] = tag_target
94
            self._set_tag_dict(td)
95
        finally:
2220.2.14 by mbp at sourcefrog
cleanup
96
            self.branch.unlock()
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
97
98
    def lookup_tag(self, tag_name):
99
        """Return the referent string of a tag"""
100
        td = self.get_tag_dict()
101
        try:
102
            return td[tag_name]
103
        except KeyError:
104
            raise errors.NoSuchTag(tag_name)
105
106
    def get_tag_dict(self):
2220.2.14 by mbp at sourcefrog
cleanup
107
        self.branch.lock_read()
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
108
        try:
2220.2.27 by Martin Pool
Start adding tags to Branch6
109
            try:
110
                tag_content = self.branch._transport.get_bytes('tags')
111
            except errors.NoSuchFile, e:
2220.2.28 by Martin Pool
Integrate tags with Branch6:
112
                # ugly, but only abentley should see this :)
113
                trace.warning('No branch/tags file in %s.  '
2220.2.27 by Martin Pool
Start adding tags to Branch6
114
                     'This branch was probably created by bzr 0.15pre.  '
2220.2.28 by Martin Pool
Integrate tags with Branch6:
115
                     'Create an empty file to silence this message.'
116
                     % (self.branch, ))
2220.2.27 by Martin Pool
Start adding tags to Branch6
117
                return {}
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
118
            return self._deserialize_tag_dict(tag_content)
119
        finally:
2220.2.14 by mbp at sourcefrog
cleanup
120
            self.branch.unlock()
2388.1.11 by Alexander Belchenko
changes after John's review
121
2388.1.1 by Erik Bagfors
created reverse_tag_dict in tags.py
122
    def get_reverse_tag_dict(self):
123
        """Returns a dict with revisions as keys
124
           and a list of tags for that revision as value"""
125
        d = self.get_tag_dict()
126
        rev = {}
127
        for key in d:
128
            try:
129
                rev[d[key]].append(key)
130
            except KeyError:
131
                rev[d[key]] = [key]
132
        return rev
133
2220.2.21 by Martin Pool
Add tag --delete command and implementation
134
    def delete_tag(self, tag_name):
135
        """Delete a tag definition.
136
        """
137
        self.branch.lock_write()
138
        try:
139
            d = self.get_tag_dict()
140
            try:
141
                del d[tag_name]
142
            except KeyError:
143
                raise errors.NoSuchTag(tag_name)
2805.5.2 by Martin Pool
Setting and deleting tags should also update the master branch, if any.
144
            master = self.branch.get_master_branch()
145
            if master is not None:
146
                try:
147
                    master.tags.delete_tag(tag_name)
148
                except errors.NoSuchTag:
149
                    pass
2220.2.21 by Martin Pool
Add tag --delete command and implementation
150
            self._set_tag_dict(d)
151
        finally:
152
            self.branch.unlock()
153
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
154
    def _set_tag_dict(self, new_dict):
155
        """Replace all tag definitions
156
157
        :param new_dict: Dictionary from tag name to target.
158
        """
2805.4.1 by Martin Pool
Old fix from Alexander: set_tag_dict should lock the branch
159
        self.branch.lock_write()
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
160
        try:
2220.2.16 by mbp at sourcefrog
Make Branch._transport be the branch's control file transport
161
            self.branch._transport.put_bytes('tags',
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
162
                self._serialize_tag_dict(new_dict))
163
        finally:
2220.2.14 by mbp at sourcefrog
cleanup
164
            self.branch.unlock()
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
165
166
    def _serialize_tag_dict(self, tag_dict):
2220.2.21 by Martin Pool
Add tag --delete command and implementation
167
        td = dict((k.encode('utf-8'), v)
2805.5.2 by Martin Pool
Setting and deleting tags should also update the master branch, if any.
168
                  for k,v in tag_dict.items())
2220.2.21 by Martin Pool
Add tag --delete command and implementation
169
        return bencode.bencode(td)
2220.2.11 by mbp at sourcefrog
Get tag tests working again, stored in the Branch
170
171
    def _deserialize_tag_dict(self, tag_content):
172
        """Convert the tag file into a dictionary of tags"""
2220.2.28 by Martin Pool
Integrate tags with Branch6:
173
        # was a special case to make initialization easy, an empty definition
2220.2.15 by mbp at sourcefrog
Store tag dictionary in bencode and accomodate non-ascii tags
174
        # is an empty dictionary
175
        if tag_content == '':
176
            return {}
177
        try:
2220.2.21 by Martin Pool
Add tag --delete command and implementation
178
            r = {}
179
            for k, v in bencode.bdecode(tag_content).items():
180
                r[k.decode('utf-8')] = v
181
            return r
182
        except ValueError, e:
183
            raise ValueError("failed to deserialize tag dictionary %r: %s"
2220.2.32 by Martin Pool
Slightly smarter tag merge
184
                % (tag_content, e))
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
185
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
186
    def merge_to(self, to_tags, overwrite=False):
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
187
        """Copy tags between repositories if necessary and possible.
188
        
189
        This method has common command-line behaviour about handling 
190
        error cases.
191
2220.2.32 by Martin Pool
Slightly smarter tag merge
192
        All new definitions are copied across, except that tags that already
193
        exist keep their existing definitions.
194
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
195
        :param to_tags: Branch to receive these tags
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
196
        :param overwrite: Overwrite conflicting tags in the target branch
2220.2.33 by Martin Pool
Start support for flagging tag conflicts
197
198
        :returns: A list of tags that conflicted, each of which is 
199
            (tagname, source_target, dest_target).
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
200
        """
201
        if self.branch == to_tags.branch:
202
            return
203
        if not self.supports_tags():
204
            # obviously nothing to copy
205
            return
2220.2.32 by Martin Pool
Slightly smarter tag merge
206
        source_dict = self.get_tag_dict()
207
        if not source_dict:
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
208
            # no tags in the source, and we don't want to clobber anything
209
            # that's in the destination
210
            return
211
        to_tags.branch.lock_write()
212
        try:
2220.2.32 by Martin Pool
Slightly smarter tag merge
213
            dest_dict = to_tags.get_tag_dict()
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
214
            result, conflicts = self._reconcile_tags(source_dict, dest_dict,
215
                                                     overwrite)
2220.2.32 by Martin Pool
Slightly smarter tag merge
216
            if result != dest_dict:
217
                to_tags._set_tag_dict(result)
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
218
        finally:
219
            to_tags.branch.unlock()
2220.2.33 by Martin Pool
Start support for flagging tag conflicts
220
        return conflicts
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
221
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
222
    def _reconcile_tags(self, source_dict, dest_dict, overwrite):
2220.2.33 by Martin Pool
Start support for flagging tag conflicts
223
        """Do a two-way merge of two tag dictionaries.
224
225
        only in source => source value
226
        only in destination => destination value
227
        same definitions => that
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
228
        different definitions => if overwrite is False, keep destination
229
            value and give a warning, otherwise use the source value
2220.2.33 by Martin Pool
Start support for flagging tag conflicts
230
231
        :returns: (result_dict,
232
            [(conflicting_tag, source_target, dest_target)])
233
        """
234
        conflicts = []
235
        result = dict(dest_dict) # copy
236
        for name, target in source_dict.items():
2804.3.1 by Lukáš Lalinský
Overwrite conflicting tags by push|pull --overwrite.
237
            if name not in result or overwrite:
2220.2.33 by Martin Pool
Start support for flagging tag conflicts
238
                result[name] = target
239
            elif result[name] == target:
240
                pass
241
            else:
242
                conflicts.append((name, target, result[name]))
243
        return result, conflicts
2220.2.32 by Martin Pool
Slightly smarter tag merge
244
2220.2.30 by Martin Pool
split out tag-merging code and add some tests
245
246
def _merge_tags_if_possible(from_branch, to_branch):
247
    from_branch.tags.merge_to(to_branch.tags)