22
22
Branch.tags.add('name', 'value')
25
from __future__ import absolute_import
25
27
# NOTE: I was going to call this tags.py, but vim seems to think all files
26
28
# called tags* are ctags files... mbp 20070220.
47
49
def __init__(self, branch):
48
50
self.branch = branch
52
def get_tag_dict(self):
53
"""Return a dictionary mapping tags to revision ids.
55
raise NotImplementedError(self.get_tag_dict)
57
def get_reverse_tag_dict(self):
58
"""Return a dictionary mapping revision ids to list of tags.
60
raise NotImplementedError(self.get_reverse_tag_dict)
62
def merge_to(self, to_tags, overwrite=False, ignore_master=False):
63
"""Merge new tags from this tags container into another.
65
:param to_tags: Tags container to merge into
66
:param overwrite: Whether to overwrite existing, divergent, tags.
67
:param ignore_master: Do not modify the tags in the target's master
68
branch (if any). Default is false (so the master will be updated).
70
:return: Tuple with tag updates as dictionary and tag conflicts
72
raise NotImplementedError(self.merge_to)
74
def set_tag(self, tag_name, revision):
77
:param tag_name: Tag name
78
:param revision: Revision id
79
:raise GhostTagsNotSupported: if revision is not present in
82
raise NotImplementedError(self.set_tag)
84
def lookup_tag(self, tag_name):
87
:param tag_name: Tag to look up
88
:raise NoSuchTag: Raised when tag does not exist
89
:return: Matching revision id
91
raise NotImplementedError(self.lookup_tag)
93
def delete_tag(self, tag_name):
96
:param tag_name: Tag to delete
97
:raise NoSuchTag: Raised when tag does not exist
99
raise NotImplementedError(self.delete_tag)
101
def rename_revisions(self, rename_map):
102
"""Replace revision ids according to a rename map.
104
:param rename_map: Dictionary mapping old revision ids to
107
raise NotImplementedError(self.rename_revisions)
50
109
def has_tag(self, tag_name):
51
110
return self.get_tag_dict().has_key(tag_name)
69
128
def merge_to(self, to_tags, overwrite=False, ignore_master=False):
70
129
# we never have anything to copy
73
132
def rename_revisions(self, rename_map):
74
133
# No tags, so nothing to rename
201
260
branch (if any). Default is false (so the master will be updated).
204
:returns: A set of tags that conflicted, each of which is
263
:returns: Tuple with tag_updates and tag_conflicts.
264
tag_updates is a dictionary with new tags, None is used for
266
tag_conflicts is a set of tags that conflicted, each of which is
205
267
(tagname, source_target, dest_target), or None if no copying was
211
273
def _merge_to_operation(self, operation, to_tags, overwrite, ignore_master):
212
274
add_cleanup = operation.add_cleanup
213
275
if self.branch == to_tags.branch:
215
277
if not self.branch.supports_tags():
216
278
# obviously nothing to copy
218
280
source_dict = self.get_tag_dict()
219
281
if not source_dict:
220
282
# no tags in the source, and we don't want to clobber anything
221
283
# that's in the destination
223
285
# We merge_to both master and child individually.
225
287
# It's possible for master and child to have differing sets of
239
301
master = to_tags.branch.get_master_branch()
240
302
if master is not None:
241
303
add_cleanup(master.lock_write().unlock)
242
conflicts = self._merge_to(to_tags, source_dict, overwrite)
304
updates, conflicts = self._merge_to(to_tags, source_dict, overwrite)
243
305
if master is not None:
244
conflicts += self._merge_to(master.tags, source_dict,
306
extra_updates, extra_conflicts = self._merge_to(master.tags,
307
source_dict, overwrite)
308
updates.update(extra_updates)
309
conflicts += extra_conflicts
246
310
# We use set() to remove any duplicate conflicts from the master
248
return set(conflicts)
312
return updates, set(conflicts)
250
314
def _merge_to(self, to_tags, source_dict, overwrite):
251
315
dest_dict = to_tags.get_tag_dict()
252
result, conflicts = self._reconcile_tags(source_dict, dest_dict,
316
result, updates, conflicts = self._reconcile_tags(source_dict,
317
dest_dict, overwrite)
254
318
if result != dest_dict:
255
319
to_tags._set_tag_dict(result)
320
return updates, conflicts
258
322
def rename_revisions(self, rename_map):
259
323
"""Rename revisions in this tags dictionary.
261
325
:param rename_map: Dictionary mapping old revids to new revids
263
327
reverse_tags = self.get_reverse_tag_dict()
275
339
* different definitions => if overwrite is False, keep destination
276
340
value and give a warning, otherwise use the source value
278
:returns: (result_dict,
342
:returns: (result_dict, updates,
279
343
[(conflicting_tag, source_target, dest_target)])
282
347
result = dict(dest_dict) # copy
283
348
for name, target in source_dict.items():
284
if name not in result or overwrite:
349
if result.get(name) == target:
351
elif name not in result or overwrite:
352
updates[name] = target
285
353
result[name] = target
286
elif result[name] == target:
289
355
conflicts.append((name, target, result[name]))
290
return result, conflicts
356
return result, updates, conflicts
293
359
def _merge_tags_if_possible(from_branch, to_branch, ignore_master=False):