1
# Copyright (C) 2007 Canonical Ltd
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.
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.
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
19
These are contained within a branch and normally constructed
20
when the branch is opened. Clients should typically do
22
Branch.tags.add('name', 'value')
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.
29
from warnings import warn
35
from bzrlib.util import bencode
40
def __init__(self, branch):
43
def has_tag(self, tag_name):
44
return self.get_tag_dict().has_key(tag_name)
47
class DisabledTags(_Tags):
48
"""Tag storage that refuses to store anything.
50
This is used by older formats that can't store tags.
53
def _not_supported(self, *a, **k):
54
raise errors.TagsNotSupported(self.branch)
56
def supports_tags(self):
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
65
def merge_to(self, to_tags):
66
# we never have anything to copy
70
class BasicTags(_Tags):
71
"""Tag storage in an unversioned branch control file.
74
def supports_tags(self):
77
def set_tag(self, tag_name, tag_target):
78
"""Add a tag definition to the branch.
80
Behaviour if the tag is already present is not defined (yet).
82
# all done with a write lock held, so this looks atomic
83
self.branch.lock_write()
85
td = self.get_tag_dict()
86
td[tag_name] = tag_target
87
self._set_tag_dict(td)
91
def lookup_tag(self, tag_name):
92
"""Return the referent string of a tag"""
93
td = self.get_tag_dict()
97
raise errors.NoSuchTag(tag_name)
99
def get_tag_dict(self):
100
self.branch.lock_read()
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.'
111
return self._deserialize_tag_dict(tag_content)
115
def get_reverse_tag_dict(self):
116
"""Returns a dict with revisions as keys
117
and a list of tags for that revision as value"""
118
d = self.get_tag_dict()
122
rev[d[key]].append(key)
127
def delete_tag(self, tag_name):
128
"""Delete a tag definition.
130
self.branch.lock_write()
132
d = self.get_tag_dict()
136
raise errors.NoSuchTag(tag_name)
137
self._set_tag_dict(d)
141
def _set_tag_dict(self, new_dict):
142
"""Replace all tag definitions
144
:param new_dict: Dictionary from tag name to target.
146
self.branch.lock_read()
148
self.branch._transport.put_bytes('tags',
149
self._serialize_tag_dict(new_dict))
153
def _serialize_tag_dict(self, tag_dict):
154
td = dict((k.encode('utf-8'), v)
155
for k,v in tag_dict.items())
156
return bencode.bencode(td)
158
def _deserialize_tag_dict(self, tag_content):
159
"""Convert the tag file into a dictionary of tags"""
160
# was a special case to make initialization easy, an empty definition
161
# is an empty dictionary
162
if tag_content == '':
166
for k, v in bencode.bdecode(tag_content).items():
167
r[k.decode('utf-8')] = v
169
except ValueError, e:
170
raise ValueError("failed to deserialize tag dictionary %r: %s"
173
def merge_to(self, to_tags):
174
"""Copy tags between repositories if necessary and possible.
176
This method has common command-line behaviour about handling
179
All new definitions are copied across, except that tags that already
180
exist keep their existing definitions.
182
:param to_tags: Branch to receive these tags
183
:param just_warn: If the destination doesn't support tags and the
184
source does have tags, just give a warning. Otherwise, raise
185
TagsNotSupported (default).
187
:returns: A list of tags that conflicted, each of which is
188
(tagname, source_target, dest_target).
190
if self.branch == to_tags.branch:
192
if not self.supports_tags():
193
# obviously nothing to copy
195
source_dict = self.get_tag_dict()
197
# no tags in the source, and we don't want to clobber anything
198
# that's in the destination
200
to_tags.branch.lock_write()
202
dest_dict = to_tags.get_tag_dict()
203
result, conflicts = self._reconcile_tags(source_dict, dest_dict)
204
if result != dest_dict:
205
to_tags._set_tag_dict(result)
207
to_tags.branch.unlock()
210
def _reconcile_tags(self, source_dict, dest_dict):
211
"""Do a two-way merge of two tag dictionaries.
213
only in source => source value
214
only in destination => destination value
215
same definitions => that
216
different definitions => keep destination value, give a warning
218
:returns: (result_dict,
219
[(conflicting_tag, source_target, dest_target)])
222
result = dict(dest_dict) # copy
223
for name, target in source_dict.items():
224
if name not in result:
225
result[name] = target
226
elif result[name] == target:
229
conflicts.append((name, target, result[name]))
230
return result, conflicts
233
def _merge_tags_if_possible(from_branch, to_branch):
234
from_branch.tags.merge_to(to_branch.tags)