57
57
def __init__(self):
58
58
hooks.Hooks.__init__(self)
59
59
self.create_hook(hooks.HookPoint('merge_file_content',
60
"Called when file content needs to be merged (including when one "
61
"side has deleted the file and the other has changed it)."
62
"merge_file_content is called with a "
63
"bzrlib.merge.MergeHookParams. The function should return a tuple "
64
"of (status, lines), where status is one of 'not_applicable', "
65
"'success', 'conflicted', or 'delete'. If status is success or "
66
"conflicted, then lines should be an iterable of strings of the "
60
"Called with a bzrlib.merge.Merger object to create a per file "
61
"merge object when starting a merge. "
62
"Should return either None or a subclass of "
63
"``bzrlib.merge.AbstractPerFileMerger``. "
64
"Such objects will then be called per file "
65
"that needs to be merged (including when one "
66
"side has deleted the file and the other has changed it). "
67
"See the AbstractPerFileMerger API docs for details on how it is "
72
class AbstractPerFileMerger(object):
73
"""PerFileMerger objects are used by plugins extending merge for bzrlib.
75
See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
77
:ivar merger: The Merge3Merger performing the merge.
80
def __init__(self, merger):
81
"""Create a PerFileMerger for use with merger."""
84
def merge_contents(self, merge_params):
85
"""Attempt to merge the contents of a single file.
87
:param merge_params: A bzrlib.merge.MergeHookParams
88
:return : A tuple of (status, chunks), where status is one of
89
'not_applicable', 'success', 'conflicted', or 'delete'. If status
90
is 'success' or 'conflicted', then chunks should be an iterable of
91
strings for the new file contents.
93
return ('not applicable', None)
96
class ConfigurableFileMerger(AbstractPerFileMerger):
97
"""Merge individual files when configured via a .conf file.
99
This is a base class for concrete custom file merging logic. Concrete
100
classes should implement ``merge_text``.
102
:ivar affected_files: The configured file paths to merge.
103
:cvar name_prefix: The prefix to use when looking up configuration
105
:cvar default_files: The default file paths to merge when no configuration
112
def __init__(self, merger):
113
super(ConfigurableFileMerger, self).__init__(merger)
114
self.affected_files = None
115
self.default_files = self.__class__.default_files or []
116
self.name_prefix = self.__class__.name_prefix
117
if self.name_prefix is None:
118
raise ValueError("name_prefix must be set.")
120
def filename_matches_config(self, params):
121
affected_files = self.affected_files
122
if affected_files is None:
123
config = self.merger.this_tree.branch.get_config()
124
# Until bzr provides a better policy for caching the config, we
125
# just add the part we're interested in to the params to avoid
126
# reading the config files repeatedly (bazaar.conf, location.conf,
128
config_key = self.name_prefix + '_merge_files'
129
affected_files = config.get_user_option_as_list(config_key)
130
if affected_files is None:
131
# If nothing was specified in the config, use the default.
132
affected_files = self.default_files
133
self.affected_files = affected_files
135
filename = self.merger.this_tree.id2path(params.file_id)
136
if filename in affected_files:
140
def merge_contents(self, params):
141
"""Merge the contents of a single file."""
142
# First, check whether this custom merge logic should be used. We
143
# expect most files should not be merged by this handler.
145
# OTHER is a straight winner, rely on default merge.
146
params.winner == 'other' or
147
# THIS and OTHER aren't both files.
148
not params.is_file_merge() or
149
# The filename isn't listed in the 'NAME_merge_files' config
151
not self.filename_matches_config(params)):
152
return 'not_applicable', None
153
return self.merge_text(self, params)
155
def merge_text(self, params):
156
"""Merge the byte contents of a single file.
158
This is called after checking that the merge should be performed in
159
merge_contents, and it should behave as per
160
``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
162
raise NotImplementedError(self.merge_text)
71
165
class MergeHookParams(object):
72
166
"""Object holding parameters passed to merge_file_content hooks.
74
There are 3 fields hooks can access:
168
There are some fields hooks can access:
76
:ivar merger: the Merger object
77
170
:ivar file_id: the file ID of the file being merged
78
171
:ivar trans_id: the transform ID for the merge of this file
79
172
:ivar this_kind: kind of file_id in 'this' tree
708
801
resolver = self._lca_multi_way
709
802
child_pb = ui.ui_factory.nested_progress_bar()
804
factories = Merger.hooks['merge_file_content']
805
hooks = [factory(self) for factory in factories] + [self]
806
self.active_hooks = [hook for hook in hooks if hook is not None]
711
807
for num, (file_id, changed, parents3, names3,
712
808
executable3) in enumerate(entries):
713
809
child_pb.update('Preparing file merge', num, len(entries))
714
810
self._merge_names(file_id, parents3, names3, resolver=resolver)
716
file_status = self.merge_contents(file_id)
812
file_status = self._do_merge_contents(file_id)
718
814
file_status = 'unmodified'
719
815
self._merge_executable(file_id,