22
22
Branch.tags.add('name', 'value')
25
from __future__ import absolute_import
27
25
# NOTE: I was going to call this tags.py, but vim seems to think all files
28
26
# called tags* are ctags files... mbp 20070220.
49
47
def __init__(self, branch):
50
48
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)
109
50
def has_tag(self, tag_name):
110
51
return self.get_tag_dict().has_key(tag_name)
128
69
def merge_to(self, to_tags, overwrite=False, ignore_master=False):
129
70
# we never have anything to copy
132
73
def rename_revisions(self, rename_map):
133
74
# No tags, so nothing to rename
260
201
branch (if any). Default is false (so the master will be updated).
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
204
:returns: A set of tags that conflicted, each of which is
267
205
(tagname, source_target, dest_target), or None if no copying was
273
211
def _merge_to_operation(self, operation, to_tags, overwrite, ignore_master):
274
212
add_cleanup = operation.add_cleanup
275
213
if self.branch == to_tags.branch:
277
215
if not self.branch.supports_tags():
278
216
# obviously nothing to copy
280
218
source_dict = self.get_tag_dict()
281
219
if not source_dict:
282
220
# no tags in the source, and we don't want to clobber anything
283
221
# that's in the destination
285
223
# We merge_to both master and child individually.
287
225
# It's possible for master and child to have differing sets of
301
239
master = to_tags.branch.get_master_branch()
302
240
if master is not None:
303
241
add_cleanup(master.lock_write().unlock)
304
updates, conflicts = self._merge_to(to_tags, source_dict, overwrite)
242
conflicts = self._merge_to(to_tags, source_dict, overwrite)
305
243
if master is not None:
306
extra_updates, extra_conflicts = self._merge_to(master.tags,
307
source_dict, overwrite)
308
updates.update(extra_updates)
309
conflicts += extra_conflicts
244
conflicts += self._merge_to(master.tags, source_dict,
310
246
# We use set() to remove any duplicate conflicts from the master
312
return updates, set(conflicts)
248
return set(conflicts)
314
250
def _merge_to(self, to_tags, source_dict, overwrite):
315
251
dest_dict = to_tags.get_tag_dict()
316
result, updates, conflicts = self._reconcile_tags(source_dict,
317
dest_dict, overwrite)
252
result, conflicts = self._reconcile_tags(source_dict, dest_dict,
318
254
if result != dest_dict:
319
255
to_tags._set_tag_dict(result)
320
return updates, conflicts
322
258
def rename_revisions(self, rename_map):
323
259
"""Rename revisions in this tags dictionary.
325
261
:param rename_map: Dictionary mapping old revids to new revids
327
263
reverse_tags = self.get_reverse_tag_dict()
339
275
* different definitions => if overwrite is False, keep destination
340
276
value and give a warning, otherwise use the source value
342
:returns: (result_dict, updates,
278
:returns: (result_dict,
343
279
[(conflicting_tag, source_target, dest_target)])
347
282
result = dict(dest_dict) # copy
348
283
for name, target in source_dict.items():
349
if result.get(name) == target:
284
if name not in result or overwrite:
285
result[name] = target
286
elif result[name] == target:
351
elif name not in result or overwrite:
352
updates[name] = target
353
result[name] = target
355
289
conflicts.append((name, target, result[name]))
356
return result, updates, conflicts
290
return result, conflicts
359
293
def _merge_tags_if_possible(from_branch, to_branch, ignore_master=False):