14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from itertools import chain
19
from bzrlib.lazy_import import lazy_import
20
lazy_import(globals(), """
21
23
from bzrlib import (
22
branch as _mod_branch,
24
conflicts as _mod_conflicts,
27
26
graph as _mod_graph,
31
30
revision as _mod_revision,
41
from bzrlib.i18n import gettext
48
from bzrlib.symbol_versioning import (
34
from bzrlib.branch import Branch
35
from bzrlib.conflicts import ConflictList, Conflict
36
from bzrlib.errors import (BzrCommandError,
46
WorkingTreeNotRevision,
49
from bzrlib.graph import Graph
50
from bzrlib.merge3 import Merge3
51
from bzrlib.osutils import rename, pathjoin
52
from progress import DummyProgress, ProgressPhase
53
from bzrlib.revision import (NULL_REVISION, ensure_null)
54
from bzrlib.textfile import check_text_lines
55
from bzrlib.trace import mutter, warning, note, is_quiet
56
from bzrlib.transform import (TransformPreview, TreeTransform,
57
resolve_conflicts, cook_conflicts,
58
conflict_pass, FinalPaths, create_from_tree,
59
unique_add, ROOT_PARENT)
60
from bzrlib.versionedfile import PlanWeaveMerge
52
63
# TODO: Report back as changes are merged in
55
66
def transform_tree(from_tree, to_tree, interesting_ids=None):
56
67
from_tree.lock_tree_write()
57
operation = cleanup.OperationWithCleanups(merge_inner)
58
operation.add_cleanup(from_tree.unlock)
59
operation.run_simple(from_tree.branch, to_tree, from_tree,
60
ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
63
class MergeHooks(hooks.Hooks):
66
hooks.Hooks.__init__(self, "bzrlib.merge", "Merger.hooks")
67
self.add_hook('merge_file_content',
68
"Called with a bzrlib.merge.Merger object to create a per file "
69
"merge object when starting a merge. "
70
"Should return either None or a subclass of "
71
"``bzrlib.merge.AbstractPerFileMerger``. "
72
"Such objects will then be called per file "
73
"that needs to be merged (including when one "
74
"side has deleted the file and the other has changed it). "
75
"See the AbstractPerFileMerger API docs for details on how it is "
80
class AbstractPerFileMerger(object):
81
"""PerFileMerger objects are used by plugins extending merge for bzrlib.
83
See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
85
:ivar merger: The Merge3Merger performing the merge.
88
def __init__(self, merger):
89
"""Create a PerFileMerger for use with merger."""
92
def merge_contents(self, merge_params):
93
"""Attempt to merge the contents of a single file.
95
:param merge_params: A bzrlib.merge.MergeHookParams
96
:return: A tuple of (status, chunks), where status is one of
97
'not_applicable', 'success', 'conflicted', or 'delete'. If status
98
is 'success' or 'conflicted', then chunks should be an iterable of
99
strings for the new file contents.
101
return ('not applicable', None)
104
class PerFileMerger(AbstractPerFileMerger):
105
"""Merge individual files when self.file_matches returns True.
107
This class is intended to be subclassed. The file_matches and
108
merge_matching methods should be overridden with concrete implementations.
111
def file_matches(self, params):
112
"""Return True if merge_matching should be called on this file.
114
Only called with merges of plain files with no clear winner.
116
Subclasses must override this.
118
raise NotImplementedError(self.file_matches)
120
def get_filename(self, params, tree):
121
"""Lookup the filename (i.e. basename, not path), given a Tree (e.g.
122
self.merger.this_tree) and a MergeHookParams.
124
return osutils.basename(tree.id2path(params.file_id))
126
def get_filepath(self, params, tree):
127
"""Calculate the path to the file in a tree.
129
:param params: A MergeHookParams describing the file to merge
130
:param tree: a Tree, e.g. self.merger.this_tree.
132
return tree.id2path(params.file_id)
134
def merge_contents(self, params):
135
"""Merge the contents of a single file."""
136
# Check whether this custom merge logic should be used.
138
# OTHER is a straight winner, rely on default merge.
139
params.winner == 'other' or
140
# THIS and OTHER aren't both files.
141
not params.is_file_merge() or
142
# The filename doesn't match *.xml
143
not self.file_matches(params)):
144
return 'not_applicable', None
145
return self.merge_matching(params)
147
def merge_matching(self, params):
148
"""Merge the contents of a single file that has matched the criteria
149
in PerFileMerger.merge_contents (is a conflict, is a file,
150
self.file_matches is True).
152
Subclasses must override this.
154
raise NotImplementedError(self.merge_matching)
157
class ConfigurableFileMerger(PerFileMerger):
158
"""Merge individual files when configured via a .conf file.
160
This is a base class for concrete custom file merging logic. Concrete
161
classes should implement ``merge_text``.
163
See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
165
:ivar affected_files: The configured file paths to merge.
167
:cvar name_prefix: The prefix to use when looking up configuration
168
details. <name_prefix>_merge_files describes the files targeted by the
171
:cvar default_files: The default file paths to merge when no configuration
178
def __init__(self, merger):
179
super(ConfigurableFileMerger, self).__init__(merger)
180
self.affected_files = None
181
self.default_files = self.__class__.default_files or []
182
self.name_prefix = self.__class__.name_prefix
183
if self.name_prefix is None:
184
raise ValueError("name_prefix must be set.")
186
def file_matches(self, params):
187
"""Check whether the file should call the merge hook.
189
<name_prefix>_merge_files configuration variable is a list of files
190
that should use the hook.
192
affected_files = self.affected_files
193
if affected_files is None:
194
config = self.merger.this_branch.get_config()
195
# Until bzr provides a better policy for caching the config, we
196
# just add the part we're interested in to the params to avoid
197
# reading the config files repeatedly (bazaar.conf, location.conf,
199
config_key = self.name_prefix + '_merge_files'
200
affected_files = config.get_user_option_as_list(config_key)
201
if affected_files is None:
202
# If nothing was specified in the config, use the default.
203
affected_files = self.default_files
204
self.affected_files = affected_files
206
filepath = self.get_filepath(params, self.merger.this_tree)
207
if filepath in affected_files:
211
def merge_matching(self, params):
212
return self.merge_text(params)
214
def merge_text(self, params):
215
"""Merge the byte contents of a single file.
217
This is called after checking that the merge should be performed in
218
merge_contents, and it should behave as per
219
``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
221
raise NotImplementedError(self.merge_text)
224
class MergeHookParams(object):
225
"""Object holding parameters passed to merge_file_content hooks.
227
There are some fields hooks can access:
229
:ivar file_id: the file ID of the file being merged
230
:ivar trans_id: the transform ID for the merge of this file
231
:ivar this_kind: kind of file_id in 'this' tree
232
:ivar other_kind: kind of file_id in 'other' tree
233
:ivar winner: one of 'this', 'other', 'conflict'
236
def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
238
self._merger = merger
239
self.file_id = file_id
240
self.trans_id = trans_id
241
self.this_kind = this_kind
242
self.other_kind = other_kind
245
def is_file_merge(self):
246
"""True if this_kind and other_kind are both 'file'."""
247
return self.this_kind == 'file' and self.other_kind == 'file'
249
@decorators.cachedproperty
250
def base_lines(self):
251
"""The lines of the 'base' version of the file."""
252
return self._merger.get_lines(self._merger.base_tree, self.file_id)
254
@decorators.cachedproperty
255
def this_lines(self):
256
"""The lines of the 'this' version of the file."""
257
return self._merger.get_lines(self._merger.this_tree, self.file_id)
259
@decorators.cachedproperty
260
def other_lines(self):
261
"""The lines of the 'other' version of the file."""
262
return self._merger.get_lines(self._merger.other_tree, self.file_id)
69
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
70
interesting_ids=interesting_ids, this_tree=from_tree)
265
75
class Merger(object):
269
76
def __init__(self, this_branch, other_tree=None, base_tree=None,
270
77
this_tree=None, pb=None, change_reporter=None,
271
78
recurse='down', revision_graph=None):
452
259
if self.other_rev_id is None:
453
260
other_basis_tree = self.revision_tree(self.other_basis)
454
261
if other_basis_tree.has_changes(self.other_tree):
455
raise errors.WorkingTreeNotRevision(self.this_tree)
262
raise WorkingTreeNotRevision(self.this_tree)
456
263
other_rev_id = self.other_basis
457
264
self.other_tree = other_basis_tree
459
@deprecated_method(deprecated_in((2, 1, 0)))
460
266
def file_revisions(self, file_id):
461
267
self.ensure_revision_trees()
268
def get_id(tree, file_id):
269
revision_id = tree.inventory[file_id].revision
462
271
if self.this_rev_id is None:
463
272
if self.this_basis_tree.get_file_sha1(file_id) != \
464
273
self.this_tree.get_file_sha1(file_id):
465
raise errors.WorkingTreeNotRevision(self.this_tree)
274
raise WorkingTreeNotRevision(self.this_tree)
467
276
trees = (self.this_basis_tree, self.other_tree)
468
return [tree.get_file_revision(file_id) for tree in trees]
277
return [get_id(tree, file_id) for tree in trees]
470
@deprecated_method(deprecated_in((2, 1, 0)))
471
279
def check_basis(self, check_clean, require_commits=True):
472
280
if self.this_basis is None and require_commits is True:
473
raise errors.BzrCommandError(
474
"This branch has no commits."
475
" (perhaps you would prefer 'bzr pull')")
281
raise BzrCommandError("This branch has no commits."
282
" (perhaps you would prefer 'bzr pull')")
477
284
self.compare_basis()
478
285
if self.this_basis != self.this_rev_id:
479
286
raise errors.UncommittedChanges(self.this_tree)
481
@deprecated_method(deprecated_in((2, 1, 0)))
482
288
def compare_basis(self):
484
290
basis_tree = self.revision_tree(self.this_tree.last_revision())
593
400
self.base_rev_id = self.revision_graph.find_unique_lca(
595
sorted_lca_keys = self.revision_graph.find_merge_order(
597
if self.base_rev_id == _mod_revision.NULL_REVISION:
598
self.base_rev_id = sorted_lca_keys[0]
600
if self.base_rev_id == _mod_revision.NULL_REVISION:
601
raise errors.UnrelatedBranches()
402
self._is_criss_cross = True
403
if self.base_rev_id == NULL_REVISION:
404
raise UnrelatedBranches()
602
405
if self._is_criss_cross:
603
trace.warning('Warning: criss-cross merge encountered. See bzr'
604
' help criss-cross.')
605
trace.mutter('Criss-cross lcas: %r' % lcas)
606
if self.base_rev_id in lcas:
607
trace.mutter('Unable to find unique lca. '
608
'Fallback %r as best option.'
610
interesting_revision_ids = set(lcas)
611
interesting_revision_ids.add(self.base_rev_id)
406
warning('Warning: criss-cross merge encountered. See bzr'
407
' help criss-cross.')
408
mutter('Criss-cross lcas: %r' % lcas)
409
interesting_revision_ids = [self.base_rev_id]
410
interesting_revision_ids.extend(lcas)
612
411
interesting_trees = dict((t.get_revision_id(), t)
613
412
for t in self.this_branch.repository.revision_trees(
614
413
interesting_revision_ids))
615
414
self._cached_trees.update(interesting_trees)
616
if self.base_rev_id in lcas:
617
self.base_tree = interesting_trees[self.base_rev_id]
619
self.base_tree = interesting_trees.pop(self.base_rev_id)
415
self.base_tree = interesting_trees.pop(self.base_rev_id)
416
sorted_lca_keys = self.revision_graph.find_merge_order(
620
418
self._lca_trees = [interesting_trees[key]
621
419
for key in sorted_lca_keys]
623
421
self.base_tree = self.revision_tree(self.base_rev_id)
624
422
self.base_is_ancestor = True
625
423
self.base_is_other_ancestor = True
626
trace.mutter('Base revid: %r' % self.base_rev_id)
424
mutter('Base revid: %r' % self.base_rev_id)
628
426
def set_base(self, base_revision):
629
427
"""Set the base revision to use for the merge.
631
429
:param base_revision: A 2-list containing a path and revision number.
633
trace.mutter("doing merge() with no base_revision specified")
431
mutter("doing merge() with no base_revision specified")
634
432
if base_revision == [None, None]:
804
600
# making sure we haven't missed any corner cases.
805
601
# if lca_trees is None:
806
602
# self._lca_trees = [self.base_tree]
807
605
self.change_reporter = change_reporter
808
606
self.cherrypick = cherrypick
608
self.pp = ProgressPhase("Merge phase", 3, self.pb)
812
warnings.warn("pp argument to Merge3Merger is deprecated")
814
warnings.warn("pb argument to Merge3Merger is deprecated")
816
612
def do_merge(self):
817
operation = cleanup.OperationWithCleanups(self._do_merge)
818
613
self.this_tree.lock_tree_write()
819
operation.add_cleanup(self.this_tree.unlock)
820
614
self.base_tree.lock_read()
821
operation.add_cleanup(self.base_tree.unlock)
822
615
self.other_tree.lock_read()
823
operation.add_cleanup(self.other_tree.unlock)
826
def _do_merge(self, operation):
827
self.tt = transform.TreeTransform(self.this_tree, None)
828
operation.add_cleanup(self.tt.finalize)
829
self._compute_transform()
830
results = self.tt.apply(no_conflicts=True)
831
self.write_modified(results)
833
self.this_tree.add_conflicts(self.cooked_conflicts)
834
except errors.UnsupportedOperation:
617
self.tt = TreeTransform(self.this_tree, self.pb)
620
self._compute_transform()
622
results = self.tt.apply(no_conflicts=True)
623
self.write_modified(results)
625
self.this_tree.add_conflicts(self.cooked_conflicts)
626
except UnsupportedOperation:
631
self.other_tree.unlock()
632
self.base_tree.unlock()
633
self.this_tree.unlock()
837
636
def make_preview_transform(self):
838
operation = cleanup.OperationWithCleanups(self._make_preview_transform)
839
637
self.base_tree.lock_read()
840
operation.add_cleanup(self.base_tree.unlock)
841
638
self.other_tree.lock_read()
842
operation.add_cleanup(self.other_tree.unlock)
843
return operation.run_simple()
845
def _make_preview_transform(self):
846
self.tt = transform.TransformPreview(self.this_tree)
847
self._compute_transform()
639
self.tt = TransformPreview(self.this_tree)
642
self._compute_transform()
645
self.other_tree.unlock()
646
self.base_tree.unlock()
850
650
def _compute_transform(self):
856
656
resolver = self._lca_multi_way
857
657
child_pb = ui.ui_factory.nested_progress_bar()
859
factories = Merger.hooks['merge_file_content']
860
hooks = [factory(self) for factory in factories] + [self]
861
self.active_hooks = [hook for hook in hooks if hook is not None]
862
659
for num, (file_id, changed, parents3, names3,
863
660
executable3) in enumerate(entries):
864
child_pb.update(gettext('Preparing file merge'), num, len(entries))
661
child_pb.update('Preparing file merge', num, len(entries))
865
662
self._merge_names(file_id, parents3, names3, resolver=resolver)
867
file_status = self._do_merge_contents(file_id)
664
file_status = self.merge_contents(file_id)
869
666
file_status = 'unmodified'
870
667
self._merge_executable(file_id,
871
668
executable3, file_status, resolver=resolver)
873
670
child_pb.finished()
874
self.tt.fixup_new_roots()
875
self._finish_computing_transform()
877
def _finish_computing_transform(self):
878
"""Finalize the transform and report the changes.
880
This is the second half of _compute_transform.
882
673
child_pb = ui.ui_factory.nested_progress_bar()
884
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
885
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
675
fs_conflicts = resolve_conflicts(self.tt, child_pb,
676
lambda t, c: conflict_pass(t, c, self.other_tree))
887
678
child_pb.finished()
888
679
if self.change_reporter is not None:
1109
901
other_root = self.tt.trans_id_file_id(other_root_file_id)
1110
902
if other_root == self.tt.root:
1112
if self.this_tree.inventory.has_id(
1113
self.other_tree.inventory.root.file_id):
1114
# the other tree's root is a non-root in the current tree (as
1115
# when a previously unrelated branch is merged into another)
1117
if self.tt.final_kind(other_root) is not None:
1118
other_root_is_present = True
1120
# other_root doesn't have a physical representation. We still need
1121
# to move any references to the actual root of the tree.
1122
other_root_is_present = False
1123
# 'other_tree.inventory.root' is not present in this tree. We are
1124
# calling adjust_path for children which *want* to be present with a
1125
# correct place to go.
1126
for _, child in self.other_tree.inventory.root.children.iteritems():
905
self.tt.final_kind(other_root)
908
if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
909
# the other tree's root is a non-root in the current tree
911
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
912
self.tt.cancel_creation(other_root)
913
self.tt.cancel_versioning(other_root)
915
def reparent_children(self, ie, target):
916
for thing, child in ie.children.iteritems():
1127
917
trans_id = self.tt.trans_id_file_id(child.file_id)
1128
if not other_root_is_present:
1129
if self.tt.final_kind(trans_id) is not None:
1130
# The item exist in the final tree and has a defined place
1133
# Move the item into the root
1135
final_name = self.tt.final_name(trans_id)
1136
except errors.NoFinalPath:
1137
# This file is not present anymore, ignore it.
1139
self.tt.adjust_path(final_name, self.tt.root, trans_id)
1140
if other_root_is_present:
1141
self.tt.cancel_creation(other_root)
1142
self.tt.cancel_versioning(other_root)
918
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1144
920
def write_modified(self, results):
1145
921
modified_hashes = {}
1313
1086
parent_id_winner = "other"
1314
1087
if name_winner == "this" and parent_id_winner == "this":
1316
if name_winner == 'conflict' or parent_id_winner == 'conflict':
1317
# Creating helpers (.OTHER or .THIS) here cause problems down the
1318
# road if a ContentConflict needs to be created so we should not do
1320
trans_id = self.tt.trans_id_file_id(file_id)
1321
self._raw_conflicts.append(('path conflict', trans_id, file_id,
1322
this_parent, this_name,
1323
other_parent, other_name))
1324
if not self.other_tree.has_id(file_id):
1089
if name_winner == "conflict":
1090
trans_id = self.tt.trans_id_file_id(file_id)
1091
self._raw_conflicts.append(('name conflict', trans_id,
1092
this_name, other_name))
1093
if parent_id_winner == "conflict":
1094
trans_id = self.tt.trans_id_file_id(file_id)
1095
self._raw_conflicts.append(('parent conflict', trans_id,
1096
this_parent, other_parent))
1097
if other_name is None:
1325
1098
# it doesn't matter whether the result was 'other' or
1326
# 'conflict'-- if it has no file id, we leave it alone.
1099
# 'conflict'-- if there's no 'other', we leave it alone.
1101
# if we get here, name_winner and parent_winner are set to safe values.
1102
trans_id = self.tt.trans_id_file_id(file_id)
1328
1103
parent_id = parents[self.winner_idx[parent_id_winner]]
1329
name = names[self.winner_idx[name_winner]]
1330
if parent_id is not None or name is not None:
1331
# if we get here, name_winner and parent_winner are set to safe
1333
if parent_id is None and name is not None:
1334
# if parent_id is None and name is non-None, current file is
1336
if names[self.winner_idx[parent_id_winner]] != '':
1337
raise AssertionError(
1338
'File looks like a root, but named %s' %
1339
names[self.winner_idx[parent_id_winner]])
1340
parent_trans_id = transform.ROOT_PARENT
1342
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1343
self.tt.adjust_path(name, parent_trans_id,
1344
self.tt.trans_id_file_id(file_id))
1104
if parent_id is not None:
1105
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1106
self.tt.adjust_path(names[self.winner_idx[name_winner]],
1107
parent_trans_id, trans_id)
1346
def _do_merge_contents(self, file_id):
1109
def merge_contents(self, file_id):
1347
1110
"""Performs a merge on file_id contents."""
1348
1111
def contents_pair(tree):
1349
if not tree.has_id(file_id):
1112
if file_id not in tree:
1350
1113
return (None, None)
1351
1114
kind = tree.kind(file_id)
1352
1115
if kind == "file":
1378
1153
if winner == 'this':
1379
1154
# No interesting changes introduced by OTHER
1380
1155
return "unmodified"
1381
# We have a hypothetical conflict, but if we have files, then we
1382
# can try to merge the content
1383
1156
trans_id = self.tt.trans_id_file_id(file_id)
1384
params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1385
other_pair[0], winner)
1386
hooks = self.active_hooks
1387
hook_status = 'not_applicable'
1389
hook_status, lines = hook.merge_contents(params)
1390
if hook_status != 'not_applicable':
1391
# Don't try any more hooks, this one applies.
1393
# If the merge ends up replacing the content of the file, we get rid of
1394
# it at the end of this method (this variable is used to track the
1395
# exceptions to this rule).
1398
if hook_status == 'not_applicable':
1399
# No merge hook was able to resolve the situation. Two cases exist:
1400
# a content conflict or a duplicate one.
1402
name = self.tt.final_name(trans_id)
1403
parent_id = self.tt.final_parent(trans_id)
1405
inhibit_content_conflict = False
1406
if params.this_kind is None: # file_id is not in THIS
1407
# Is the name used for a different file_id ?
1408
dupe_path = self.other_tree.id2path(file_id)
1409
this_id = self.this_tree.path2id(dupe_path)
1410
if this_id is not None:
1411
# Two entries for the same path
1413
# versioning the merged file will trigger a duplicate
1415
self.tt.version_file(file_id, trans_id)
1416
transform.create_from_tree(
1417
self.tt, trans_id, self.other_tree, file_id,
1418
filter_tree_path=self._get_filter_tree_path(file_id))
1419
inhibit_content_conflict = True
1420
elif params.other_kind is None: # file_id is not in OTHER
1421
# Is the name used for a different file_id ?
1422
dupe_path = self.this_tree.id2path(file_id)
1423
other_id = self.other_tree.path2id(dupe_path)
1424
if other_id is not None:
1425
# Two entries for the same path again, but here, the other
1426
# entry will also be merged. We simply inhibit the
1427
# 'content' conflict creation because we know OTHER will
1428
# create (or has already created depending on ordering) an
1429
# entry at the same path. This will trigger a 'duplicate'
1432
inhibit_content_conflict = True
1433
if not inhibit_content_conflict:
1434
if params.this_kind is not None:
1435
self.tt.unversion_file(trans_id)
1436
# This is a contents conflict, because none of the available
1437
# functions could merge it.
1438
file_group = self._dump_conflicts(name, parent_id, file_id,
1440
self._raw_conflicts.append(('contents conflict', file_group))
1441
elif hook_status == 'success':
1442
self.tt.create_file(lines, trans_id)
1443
elif hook_status == 'conflicted':
1444
# XXX: perhaps the hook should be able to provide
1445
# the BASE/THIS/OTHER files?
1446
self.tt.create_file(lines, trans_id)
1447
self._raw_conflicts.append(('text conflict', trans_id))
1448
name = self.tt.final_name(trans_id)
1449
parent_id = self.tt.final_parent(trans_id)
1450
self._dump_conflicts(name, parent_id, file_id)
1451
elif hook_status == 'delete':
1452
self.tt.unversion_file(trans_id)
1454
elif hook_status == 'done':
1455
# The hook function did whatever it needs to do directly, no
1456
# further action needed here.
1459
raise AssertionError('unknown hook_status: %r' % (hook_status,))
1460
if not self.this_tree.has_id(file_id) and result == "modified":
1461
self.tt.version_file(file_id, trans_id)
1463
# The merge has been performed and produced a new content, so the
1464
# old contents should not be retained.
1465
self.tt.delete_contents(trans_id)
1468
def _default_other_winner_merge(self, merge_hook_params):
1469
"""Replace this contents with other."""
1470
file_id = merge_hook_params.file_id
1471
trans_id = merge_hook_params.trans_id
1472
if self.other_tree.has_id(file_id):
1473
# OTHER changed the file
1474
transform.create_from_tree(
1475
self.tt, trans_id, self.other_tree, file_id,
1476
filter_tree_path=self._get_filter_tree_path(file_id))
1478
elif self.this_tree.has_id(file_id):
1479
# OTHER deleted the file
1480
return 'delete', None
1482
raise AssertionError(
1483
'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1486
def merge_contents(self, merge_hook_params):
1487
"""Fallback merge logic after user installed hooks."""
1488
# This function is used in merge hooks as the fallback instance.
1489
# Perhaps making this function and the functions it calls be a
1490
# a separate class would be better.
1491
if merge_hook_params.winner == 'other':
1157
if winner == 'other':
1492
1158
# OTHER is a straight winner, so replace this contents with other
1493
return self._default_other_winner_merge(merge_hook_params)
1494
elif merge_hook_params.is_file_merge():
1495
# THIS and OTHER are both files, so text merge. Either
1496
# BASE is a file, or both converted to files, so at least we
1497
# have agreement that output should be a file.
1499
self.text_merge(merge_hook_params.file_id,
1500
merge_hook_params.trans_id)
1501
except errors.BinaryFile:
1502
return 'not_applicable', None
1159
file_in_this = file_id in self.this_tree
1161
# Remove any existing contents
1162
self.tt.delete_contents(trans_id)
1163
if file_id in self.other_tree:
1164
# OTHER changed the file
1165
create_from_tree(self.tt, trans_id,
1166
self.other_tree, file_id)
1167
if not file_in_this:
1168
self.tt.version_file(file_id, trans_id)
1171
# OTHER deleted the file
1172
self.tt.unversion_file(trans_id)
1505
return 'not_applicable', None
1175
# We have a hypothetical conflict, but if we have files, then we
1176
# can try to merge the content
1177
if this_pair[0] == 'file' and other_pair[0] == 'file':
1178
# THIS and OTHER are both files, so text merge. Either
1179
# BASE is a file, or both converted to files, so at least we
1180
# have agreement that output should be a file.
1182
self.text_merge(file_id, trans_id)
1184
return contents_conflict()
1185
if file_id not in self.this_tree:
1186
self.tt.version_file(file_id, trans_id)
1188
self.tt.tree_kind(trans_id)
1189
self.tt.delete_contents(trans_id)
1194
return contents_conflict()
1507
1196
def get_lines(self, tree, file_id):
1508
1197
"""Return the lines in a file, or an empty list."""
1509
if tree.has_id(file_id):
1510
return tree.get_file_lines(file_id)
1199
return tree.get_file(file_id).readlines()
1658
1321
def cook_conflicts(self, fs_conflicts):
1659
1322
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1660
content_conflict_file_ids = set()
1661
cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
1662
fp = transform.FinalPaths(self.tt)
1323
from conflicts import Conflict
1325
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1326
fp = FinalPaths(self.tt)
1663
1327
for conflict in self._raw_conflicts:
1664
1328
conflict_type = conflict[0]
1665
if conflict_type == 'path conflict':
1667
this_parent, this_name,
1668
other_parent, other_name) = conflict[1:]
1669
if this_parent is None or this_name is None:
1670
this_path = '<deleted>'
1672
parent_path = fp.get_path(
1673
self.tt.trans_id_file_id(this_parent))
1674
this_path = osutils.pathjoin(parent_path, this_name)
1675
if other_parent is None or other_name is None:
1676
other_path = '<deleted>'
1678
if other_parent == self.other_tree.get_root_id():
1679
# The tree transform doesn't know about the other root,
1680
# so we special case here to avoid a NoFinalPath
1684
parent_path = fp.get_path(
1685
self.tt.trans_id_file_id(other_parent))
1686
other_path = osutils.pathjoin(parent_path, other_name)
1687
c = _mod_conflicts.Conflict.factory(
1688
'path conflict', path=this_path,
1689
conflict_path=other_path,
1691
elif conflict_type == 'contents conflict':
1329
if conflict_type in ('name conflict', 'parent conflict'):
1330
trans_id = conflict[1]
1331
conflict_args = conflict[2:]
1332
if trans_id not in name_conflicts:
1333
name_conflicts[trans_id] = {}
1334
unique_add(name_conflicts[trans_id], conflict_type,
1336
if conflict_type == 'contents conflict':
1692
1337
for trans_id in conflict[1]:
1693
1338
file_id = self.tt.final_file_id(trans_id)
1694
1339
if file_id is not None:
1695
# Ok we found the relevant file-id
1697
1341
path = fp.get_path(trans_id)
1698
1342
for suffix in ('.BASE', '.THIS', '.OTHER'):
1699
1343
if path.endswith(suffix):
1700
# Here is the raw path
1701
1344
path = path[:-len(suffix)]
1703
c = _mod_conflicts.Conflict.factory(conflict_type,
1704
path=path, file_id=file_id)
1705
content_conflict_file_ids.add(file_id)
1706
elif conflict_type == 'text conflict':
1346
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1347
self.cooked_conflicts.append(c)
1348
if conflict_type == 'text conflict':
1707
1349
trans_id = conflict[1]
1708
1350
path = fp.get_path(trans_id)
1709
1351
file_id = self.tt.final_file_id(trans_id)
1710
c = _mod_conflicts.Conflict.factory(conflict_type,
1711
path=path, file_id=file_id)
1352
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1353
self.cooked_conflicts.append(c)
1355
for trans_id, conflicts in name_conflicts.iteritems():
1357
this_parent, other_parent = conflicts['parent conflict']
1358
if this_parent == other_parent:
1359
raise AssertionError()
1361
this_parent = other_parent = \
1362
self.tt.final_file_id(self.tt.final_parent(trans_id))
1364
this_name, other_name = conflicts['name conflict']
1365
if this_name == other_name:
1366
raise AssertionError()
1368
this_name = other_name = self.tt.final_name(trans_id)
1369
other_path = fp.get_path(trans_id)
1370
if this_parent is not None and this_name is not None:
1371
this_parent_path = \
1372
fp.get_path(self.tt.trans_id_file_id(this_parent))
1373
this_path = pathjoin(this_parent_path, this_name)
1713
raise AssertionError('bad conflict type: %r' % (conflict,))
1714
cooked_conflicts.append(c)
1716
self.cooked_conflicts = []
1717
# We want to get rid of path conflicts when a corresponding contents
1718
# conflict exists. This can occur when one branch deletes a file while
1719
# the other renames *and* modifies it. In this case, the content
1720
# conflict is enough.
1721
for c in cooked_conflicts:
1722
if (c.typestring == 'path conflict'
1723
and c.file_id in content_conflict_file_ids):
1375
this_path = "<deleted>"
1376
file_id = self.tt.final_file_id(trans_id)
1377
c = Conflict.factory('path conflict', path=this_path,
1378
conflict_path=other_path, file_id=file_id)
1725
1379
self.cooked_conflicts.append(c)
1726
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1380
self.cooked_conflicts.sort(key=Conflict.sort_key)
1729
1383
class WeaveMerger(Merge3Merger):
1733
1387
supports_reverse_cherrypick = False
1734
1388
history_based = True
1736
def _generate_merge_plan(self, file_id, base):
1737
return self.this_tree.plan_file_merge(file_id, self.other_tree,
1390
def _merged_lines(self, file_id):
1391
"""Generate the merged lines.
1392
There is no distinction between lines that are meant to contain <<<<<<<
1396
base = self.base_tree
1399
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1740
def _merged_lines(self, file_id):
1741
"""Generate the merged lines.
1742
There is no distinction between lines that are meant to contain <<<<<<<
1746
base = self.base_tree
1749
plan = self._generate_merge_plan(file_id, base)
1750
1401
if 'merge' in debug.debug_flags:
1751
1402
plan = list(plan)
1752
1403
trans_id = self.tt.trans_id_file_id(file_id)
1753
1404
name = self.tt.final_name(trans_id) + '.plan'
1754
contents = ('%11s|%s' % l for l in plan)
1405
contents = ('%10s|%s' % l for l in plan)
1755
1406
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1756
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1757
'>>>>>>> MERGE-SOURCE\n')
1758
lines, conflicts = textmerge.merge_lines(self.reprocess)
1760
base_lines = textmerge.base_from_plan()
1763
return lines, base_lines
1407
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1408
'>>>>>>> MERGE-SOURCE\n')
1409
return textmerge.merge_lines(self.reprocess)
1765
1411
def text_merge(self, file_id, trans_id):
1766
1412
"""Perform a (weave) text merge for a given file and file-id.
1767
1413
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1768
1414
and a conflict will be noted.
1770
lines, base_lines = self._merged_lines(file_id)
1416
lines, conflicts = self._merged_lines(file_id)
1771
1417
lines = list(lines)
1772
1418
# Note we're checking whether the OUTPUT is binary in this case,
1773
1419
# because we don't want to get into weave merge guts.
1774
textfile.check_text_lines(lines)
1420
check_text_lines(lines)
1775
1421
self.tt.create_file(lines, trans_id)
1776
if base_lines is not None:
1778
1423
self._raw_conflicts.append(('text conflict', trans_id))
1779
1424
name = self.tt.final_name(trans_id)
1780
1425
parent_id = self.tt.final_parent(trans_id)
1781
1426
file_group = self._dump_conflicts(name, parent_id, file_id,
1783
base_lines=base_lines)
1784
1428
file_group.append(trans_id)
1787
1431
class LCAMerger(WeaveMerger):
1789
def _generate_merge_plan(self, file_id, base):
1790
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1433
def _merged_lines(self, file_id):
1434
"""Generate the merged lines.
1435
There is no distinction between lines that are meant to contain <<<<<<<
1439
base = self.base_tree
1442
plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1444
if 'merge' in debug.debug_flags:
1446
trans_id = self.tt.trans_id_file_id(file_id)
1447
name = self.tt.final_name(trans_id) + '.plan'
1448
contents = ('%10s|%s' % l for l in plan)
1449
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1450
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1451
'>>>>>>> MERGE-SOURCE\n')
1452
return textmerge.merge_lines(self.reprocess)
1793
1455
class Diff3Merger(Merge3Merger):
1794
1456
"""Three-way merger using external diff3 for text merging"""
1796
1458
def dump_file(self, temp_dir, name, tree, file_id):
1797
out_path = osutils.pathjoin(temp_dir, name)
1459
out_path = pathjoin(temp_dir, name)
1798
1460
out_file = open(out_path, "wb")
1800
1462
in_file = tree.get_file(file_id)
1833
1495
osutils.rmtree(temp_dir)
1836
class PathNotInTree(errors.BzrError):
1838
_fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
1840
def __init__(self, path, tree):
1841
errors.BzrError.__init__(self, path=path, tree=tree)
1844
class MergeIntoMerger(Merger):
1845
"""Merger that understands other_tree will be merged into a subdir.
1847
This also changes the Merger api so that it uses real Branch, revision_id,
1848
and RevisonTree objects, rather than using revision specs.
1851
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1852
source_subpath, other_rev_id=None):
1853
"""Create a new MergeIntoMerger object.
1855
source_subpath in other_tree will be effectively copied to
1856
target_subdir in this_tree.
1858
:param this_tree: The tree that we will be merging into.
1859
:param other_branch: The Branch we will be merging from.
1860
:param other_tree: The RevisionTree object we want to merge.
1861
:param target_subdir: The relative path where we want to merge
1862
other_tree into this_tree
1863
:param source_subpath: The relative path specifying the subtree of
1864
other_tree to merge into this_tree.
1866
# It is assumed that we are merging a tree that is not in our current
1867
# ancestry, which means we are using the "EmptyTree" as our basis.
1868
null_ancestor_tree = this_tree.branch.repository.revision_tree(
1869
_mod_revision.NULL_REVISION)
1870
super(MergeIntoMerger, self).__init__(
1871
this_branch=this_tree.branch,
1872
this_tree=this_tree,
1873
other_tree=other_tree,
1874
base_tree=null_ancestor_tree,
1876
self._target_subdir = target_subdir
1877
self._source_subpath = source_subpath
1878
self.other_branch = other_branch
1879
if other_rev_id is None:
1880
other_rev_id = other_tree.get_revision_id()
1881
self.other_rev_id = self.other_basis = other_rev_id
1882
self.base_is_ancestor = True
1883
self.backup_files = True
1884
self.merge_type = Merge3Merger
1885
self.show_base = False
1886
self.reprocess = False
1887
self.interesting_ids = None
1888
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1889
target_subdir=self._target_subdir,
1890
source_subpath=self._source_subpath)
1891
if self._source_subpath != '':
1892
# If this isn't a partial merge make sure the revisions will be
1894
self._maybe_fetch(self.other_branch, self.this_branch,
1897
def set_pending(self):
1898
if self._source_subpath != '':
1900
Merger.set_pending(self)
1903
class _MergeTypeParameterizer(object):
1904
"""Wrap a merge-type class to provide extra parameters.
1906
This is hack used by MergeIntoMerger to pass some extra parameters to its
1907
merge_type. Merger.do_merge() sets up its own set of parameters to pass to
1908
the 'merge_type' member. It is difficult override do_merge without
1909
re-writing the whole thing, so instead we create a wrapper which will pass
1910
the extra parameters.
1913
def __init__(self, merge_type, **kwargs):
1914
self._extra_kwargs = kwargs
1915
self._merge_type = merge_type
1917
def __call__(self, *args, **kwargs):
1918
kwargs.update(self._extra_kwargs)
1919
return self._merge_type(*args, **kwargs)
1921
def __getattr__(self, name):
1922
return getattr(self._merge_type, name)
1925
class MergeIntoMergeType(Merge3Merger):
1926
"""Merger that incorporates a tree (or part of a tree) into another."""
1928
def __init__(self, *args, **kwargs):
1929
"""Initialize the merger object.
1931
:param args: See Merge3Merger.__init__'s args.
1932
:param kwargs: See Merge3Merger.__init__'s keyword args, except for
1933
source_subpath and target_subdir.
1934
:keyword source_subpath: The relative path specifying the subtree of
1935
other_tree to merge into this_tree.
1936
:keyword target_subdir: The relative path where we want to merge
1937
other_tree into this_tree
1939
# All of the interesting work happens during Merge3Merger.__init__(),
1940
# so we have have to hack in to get our extra parameters set.
1941
self._source_subpath = kwargs.pop('source_subpath')
1942
self._target_subdir = kwargs.pop('target_subdir')
1943
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1945
def _compute_transform(self):
1946
child_pb = ui.ui_factory.nested_progress_bar()
1948
entries = self._entries_to_incorporate()
1949
entries = list(entries)
1950
for num, (entry, parent_id) in enumerate(entries):
1951
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1952
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1953
trans_id = transform.new_by_entry(self.tt, entry,
1954
parent_trans_id, self.other_tree)
1957
self._finish_computing_transform()
1959
def _entries_to_incorporate(self):
1960
"""Yields pairs of (inventory_entry, new_parent)."""
1961
other_inv = self.other_tree.inventory
1962
subdir_id = other_inv.path2id(self._source_subpath)
1963
if subdir_id is None:
1964
# XXX: The error would be clearer if it gave the URL of the source
1965
# branch, but we don't have a reference to that here.
1966
raise PathNotInTree(self._source_subpath, "Source tree")
1967
subdir = other_inv[subdir_id]
1968
parent_in_target = osutils.dirname(self._target_subdir)
1969
target_id = self.this_tree.inventory.path2id(parent_in_target)
1970
if target_id is None:
1971
raise PathNotInTree(self._target_subdir, "Target tree")
1972
name_in_target = osutils.basename(self._target_subdir)
1973
merge_into_root = subdir.copy()
1974
merge_into_root.name = name_in_target
1975
if self.this_tree.inventory.has_id(merge_into_root.file_id):
1976
# Give the root a new file-id.
1977
# This can happen fairly easily if the directory we are
1978
# incorporating is the root, and both trees have 'TREE_ROOT' as
1979
# their root_id. Users will expect this to Just Work, so we
1980
# change the file-id here.
1981
# Non-root file-ids could potentially conflict too. That's really
1982
# an edge case, so we don't do anything special for those. We let
1983
# them cause conflicts.
1984
merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1985
yield (merge_into_root, target_id)
1986
if subdir.kind != 'directory':
1987
# No children, so we are done.
1989
for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1990
parent_id = entry.parent_id
1991
if parent_id == subdir.file_id:
1992
# The root's parent ID has changed, so make sure children of
1993
# the root refer to the new ID.
1994
parent_id = merge_into_root.file_id
1995
yield (entry, parent_id)
1998
1498
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1999
1499
backup_files=False,
2000
1500
merge_type=Merge3Merger,