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(), """
23
21
from bzrlib import (
22
branch as _mod_branch,
24
conflicts as _mod_conflicts,
26
27
graph as _mod_graph,
30
31
revision as _mod_revision,
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
41
from bzrlib.i18n import gettext
48
from bzrlib.symbol_versioning import (
63
52
# TODO: Report back as changes are merged in
66
55
def transform_tree(from_tree, to_tree, interesting_ids=None):
67
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
interesting_ids=interesting_ids, this_tree=from_tree)
56
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)
71
265
class Merger(object):
72
269
def __init__(self, this_branch, other_tree=None, base_tree=None,
73
270
this_tree=None, pb=None, change_reporter=None,
74
271
recurse='down', revision_graph=None):
244
452
if self.other_rev_id is None:
245
453
other_basis_tree = self.revision_tree(self.other_basis)
246
changes = other_basis_tree.changes_from(self.other_tree)
247
if changes.has_changed():
248
raise WorkingTreeNotRevision(self.this_tree)
454
if other_basis_tree.has_changes(self.other_tree):
455
raise errors.WorkingTreeNotRevision(self.this_tree)
249
456
other_rev_id = self.other_basis
250
457
self.other_tree = other_basis_tree
459
@deprecated_method(deprecated_in((2, 1, 0)))
252
460
def file_revisions(self, file_id):
253
461
self.ensure_revision_trees()
254
def get_id(tree, file_id):
255
revision_id = tree.inventory[file_id].revision
257
462
if self.this_rev_id is None:
258
463
if self.this_basis_tree.get_file_sha1(file_id) != \
259
464
self.this_tree.get_file_sha1(file_id):
260
raise WorkingTreeNotRevision(self.this_tree)
465
raise errors.WorkingTreeNotRevision(self.this_tree)
262
467
trees = (self.this_basis_tree, self.other_tree)
263
return [get_id(tree, file_id) for tree in trees]
468
return [tree.get_file_revision(file_id) for tree in trees]
470
@deprecated_method(deprecated_in((2, 1, 0)))
265
471
def check_basis(self, check_clean, require_commits=True):
266
472
if self.this_basis is None and require_commits is True:
267
raise BzrCommandError("This branch has no commits."
268
" (perhaps you would prefer 'bzr pull')")
473
raise errors.BzrCommandError(
474
"This branch has no commits."
475
" (perhaps you would prefer 'bzr pull')")
270
477
self.compare_basis()
271
478
if self.this_basis != self.this_rev_id:
272
479
raise errors.UncommittedChanges(self.this_tree)
481
@deprecated_method(deprecated_in((2, 1, 0)))
274
482
def compare_basis(self):
276
484
basis_tree = self.revision_tree(self.this_tree.last_revision())
277
485
except errors.NoSuchRevision:
278
486
basis_tree = self.this_tree.basis_tree()
279
changes = self.this_tree.changes_from(basis_tree)
280
if not changes.has_changed():
487
if not self.this_tree.has_changes(basis_tree):
281
488
self.this_rev_id = self.this_basis
283
490
def set_interesting_files(self, file_list):
284
491
self.interesting_files = file_list
286
493
def set_pending(self):
287
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
494
if (not self.base_is_ancestor or not self.base_is_other_ancestor
495
or self.other_rev_id is None):
289
497
self._add_parent()
291
499
def _add_parent(self):
292
500
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
293
501
new_parent_trees = []
502
operation = cleanup.OperationWithCleanups(
503
self.this_tree.set_parent_trees)
294
504
for revision_id in new_parents:
296
506
tree = self.revision_tree(revision_id)
387
593
self.base_rev_id = self.revision_graph.find_unique_lca(
389
self._is_criss_cross = True
390
if self.base_rev_id == NULL_REVISION:
391
raise UnrelatedBranches()
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()
392
602
if self._is_criss_cross:
393
warning('Warning: criss-cross merge encountered. See bzr'
394
' help criss-cross.')
395
mutter('Criss-cross lcas: %r' % lcas)
396
interesting_revision_ids = [self.base_rev_id]
397
interesting_revision_ids.extend(lcas)
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.' % self.base_rev_id)
609
interesting_revision_ids = set(lcas)
610
interesting_revision_ids.add(self.base_rev_id)
398
611
interesting_trees = dict((t.get_revision_id(), t)
399
612
for t in self.this_branch.repository.revision_trees(
400
613
interesting_revision_ids))
401
614
self._cached_trees.update(interesting_trees)
402
self.base_tree = interesting_trees.pop(self.base_rev_id)
403
sorted_lca_keys = self.revision_graph.find_merge_order(
615
if self.base_rev_id in lcas:
616
self.base_tree = interesting_trees[self.base_rev_id]
618
self.base_tree = interesting_trees.pop(self.base_rev_id)
405
619
self._lca_trees = [interesting_trees[key]
406
620
for key in sorted_lca_keys]
408
622
self.base_tree = self.revision_tree(self.base_rev_id)
409
623
self.base_is_ancestor = True
410
624
self.base_is_other_ancestor = True
411
mutter('Base revid: %r' % self.base_rev_id)
625
trace.mutter('Base revid: %r' % self.base_rev_id)
413
627
def set_base(self, base_revision):
414
628
"""Set the base revision to use for the merge.
416
630
:param base_revision: A 2-list containing a path and revision number.
418
mutter("doing merge() with no base_revision specified")
632
trace.mutter("doing merge() with no base_revision specified")
419
633
if base_revision == [None, None]:
587
802
# making sure we haven't missed any corner cases.
588
803
# if lca_trees is None:
589
804
# self._lca_trees = [self.base_tree]
592
805
self.change_reporter = change_reporter
593
806
self.cherrypick = cherrypick
595
self.pp = ProgressPhase("Merge phase", 3, self.pb)
810
warnings.warn("pp argument to Merge3Merger is deprecated")
812
warnings.warn("pb argument to Merge3Merger is deprecated")
599
814
def do_merge(self):
815
operation = cleanup.OperationWithCleanups(self._do_merge)
600
816
self.this_tree.lock_tree_write()
817
operation.add_cleanup(self.this_tree.unlock)
601
818
self.base_tree.lock_read()
819
operation.add_cleanup(self.base_tree.unlock)
602
820
self.other_tree.lock_read()
603
self.tt = TreeTransform(self.this_tree, self.pb)
821
operation.add_cleanup(self.other_tree.unlock)
824
def _do_merge(self, operation):
825
self.tt = transform.TreeTransform(self.this_tree, None)
826
operation.add_cleanup(self.tt.finalize)
827
self._compute_transform()
828
results = self.tt.apply(no_conflicts=True)
829
self.write_modified(results)
606
self._compute_transform()
608
results = self.tt.apply(no_conflicts=True)
609
self.write_modified(results)
611
self.this_tree.add_conflicts(self.cooked_conflicts)
612
except UnsupportedOperation:
616
self.other_tree.unlock()
617
self.base_tree.unlock()
618
self.this_tree.unlock()
831
self.this_tree.add_conflicts(self.cooked_conflicts)
832
except errors.UnsupportedOperation:
621
835
def make_preview_transform(self):
836
operation = cleanup.OperationWithCleanups(self._make_preview_transform)
622
837
self.base_tree.lock_read()
838
operation.add_cleanup(self.base_tree.unlock)
623
839
self.other_tree.lock_read()
624
self.tt = TransformPreview(self.this_tree)
627
self._compute_transform()
630
self.other_tree.unlock()
631
self.base_tree.unlock()
840
operation.add_cleanup(self.other_tree.unlock)
841
return operation.run_simple()
843
def _make_preview_transform(self):
844
self.tt = transform.TransformPreview(self.this_tree)
845
self._compute_transform()
635
848
def _compute_transform(self):
641
854
resolver = self._lca_multi_way
642
855
child_pb = ui.ui_factory.nested_progress_bar()
857
factories = Merger.hooks['merge_file_content']
858
hooks = [factory(self) for factory in factories] + [self]
859
self.active_hooks = [hook for hook in hooks if hook is not None]
644
860
for num, (file_id, changed, parents3, names3,
645
861
executable3) in enumerate(entries):
646
child_pb.update('Preparing file merge', num, len(entries))
862
child_pb.update(gettext('Preparing file merge'), num, len(entries))
647
863
self._merge_names(file_id, parents3, names3, resolver=resolver)
649
file_status = self.merge_contents(file_id)
865
file_status = self._do_merge_contents(file_id)
651
867
file_status = 'unmodified'
652
868
self._merge_executable(file_id,
653
869
executable3, file_status, resolver=resolver)
655
871
child_pb.finished()
872
self.tt.fixup_new_roots()
873
self._finish_computing_transform()
875
def _finish_computing_transform(self):
876
"""Finalize the transform and report the changes.
878
This is the second half of _compute_transform.
658
880
child_pb = ui.ui_factory.nested_progress_bar()
660
fs_conflicts = resolve_conflicts(self.tt, child_pb,
661
lambda t, c: conflict_pass(t, c, self.other_tree))
882
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
883
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
663
885
child_pb.finished()
664
886
if self.change_reporter is not None:
886
1107
other_root = self.tt.trans_id_file_id(other_root_file_id)
887
1108
if other_root == self.tt.root:
890
self.tt.final_kind(other_root)
893
if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
894
# the other tree's root is a non-root in the current tree
896
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
897
self.tt.cancel_creation(other_root)
898
self.tt.cancel_versioning(other_root)
900
def reparent_children(self, ie, target):
901
for thing, child in ie.children.iteritems():
1110
if self.this_tree.inventory.has_id(
1111
self.other_tree.inventory.root.file_id):
1112
# the other tree's root is a non-root in the current tree (as
1113
# when a previously unrelated branch is merged into another)
1115
if self.tt.final_kind(other_root) is not None:
1116
other_root_is_present = True
1118
# other_root doesn't have a physical representation. We still need
1119
# to move any references to the actual root of the tree.
1120
other_root_is_present = False
1121
# 'other_tree.inventory.root' is not present in this tree. We are
1122
# calling adjust_path for children which *want* to be present with a
1123
# correct place to go.
1124
for _, child in self.other_tree.inventory.root.children.iteritems():
902
1125
trans_id = self.tt.trans_id_file_id(child.file_id)
903
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1126
if not other_root_is_present:
1127
if self.tt.final_kind(trans_id) is not None:
1128
# The item exist in the final tree and has a defined place
1131
# Move the item into the root
1133
final_name = self.tt.final_name(trans_id)
1134
except errors.NoFinalPath:
1135
# This file is not present anymore, ignore it.
1137
self.tt.adjust_path(final_name, self.tt.root, trans_id)
1138
if other_root_is_present:
1139
self.tt.cancel_creation(other_root)
1140
self.tt.cancel_versioning(other_root)
905
1142
def write_modified(self, results):
906
1143
modified_hashes = {}
1071
1311
parent_id_winner = "other"
1072
1312
if name_winner == "this" and parent_id_winner == "this":
1074
if name_winner == "conflict":
1075
trans_id = self.tt.trans_id_file_id(file_id)
1076
self._raw_conflicts.append(('name conflict', trans_id,
1077
this_name, other_name))
1078
if parent_id_winner == "conflict":
1079
trans_id = self.tt.trans_id_file_id(file_id)
1080
self._raw_conflicts.append(('parent conflict', trans_id,
1081
this_parent, other_parent))
1082
if other_name is None:
1314
if name_winner == 'conflict' or parent_id_winner == 'conflict':
1315
# Creating helpers (.OTHER or .THIS) here cause problems down the
1316
# road if a ContentConflict needs to be created so we should not do
1318
trans_id = self.tt.trans_id_file_id(file_id)
1319
self._raw_conflicts.append(('path conflict', trans_id, file_id,
1320
this_parent, this_name,
1321
other_parent, other_name))
1322
if not self.other_tree.has_id(file_id):
1083
1323
# it doesn't matter whether the result was 'other' or
1084
# 'conflict'-- if there's no 'other', we leave it alone.
1324
# 'conflict'-- if it has no file id, we leave it alone.
1086
# if we get here, name_winner and parent_winner are set to safe values.
1087
trans_id = self.tt.trans_id_file_id(file_id)
1088
1326
parent_id = parents[self.winner_idx[parent_id_winner]]
1089
if parent_id is not None:
1090
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1091
self.tt.adjust_path(names[self.winner_idx[name_winner]],
1092
parent_trans_id, trans_id)
1327
name = names[self.winner_idx[name_winner]]
1328
if parent_id is not None or name is not None:
1329
# if we get here, name_winner and parent_winner are set to safe
1331
if parent_id is None and name is not None:
1332
# if parent_id is None and name is non-None, current file is
1334
if names[self.winner_idx[parent_id_winner]] != '':
1335
raise AssertionError(
1336
'File looks like a root, but named %s' %
1337
names[self.winner_idx[parent_id_winner]])
1338
parent_trans_id = transform.ROOT_PARENT
1340
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1341
self.tt.adjust_path(name, parent_trans_id,
1342
self.tt.trans_id_file_id(file_id))
1094
def merge_contents(self, file_id):
1344
def _do_merge_contents(self, file_id):
1095
1345
"""Performs a merge on file_id contents."""
1096
1346
def contents_pair(tree):
1097
if file_id not in tree:
1347
if not tree.has_id(file_id):
1098
1348
return (None, None)
1099
1349
kind = tree.kind(file_id)
1100
1350
if kind == "file":
1138
1376
if winner == 'this':
1139
1377
# No interesting changes introduced by OTHER
1140
1378
return "unmodified"
1379
# We have a hypothetical conflict, but if we have files, then we
1380
# can try to merge the content
1141
1381
trans_id = self.tt.trans_id_file_id(file_id)
1142
if winner == 'other':
1382
params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1383
other_pair[0], winner)
1384
hooks = self.active_hooks
1385
hook_status = 'not_applicable'
1387
hook_status, lines = hook.merge_contents(params)
1388
if hook_status != 'not_applicable':
1389
# Don't try any more hooks, this one applies.
1392
if hook_status == 'not_applicable':
1393
# This is a contents conflict, because none of the available
1394
# functions could merge it.
1396
name = self.tt.final_name(trans_id)
1397
parent_id = self.tt.final_parent(trans_id)
1398
if self.this_tree.has_id(file_id):
1399
self.tt.unversion_file(trans_id)
1400
file_group = self._dump_conflicts(name, parent_id, file_id,
1402
self._raw_conflicts.append(('contents conflict', file_group))
1403
elif hook_status == 'success':
1404
self.tt.create_file(lines, trans_id)
1405
elif hook_status == 'conflicted':
1406
# XXX: perhaps the hook should be able to provide
1407
# the BASE/THIS/OTHER files?
1408
self.tt.create_file(lines, trans_id)
1409
self._raw_conflicts.append(('text conflict', trans_id))
1410
name = self.tt.final_name(trans_id)
1411
parent_id = self.tt.final_parent(trans_id)
1412
self._dump_conflicts(name, parent_id, file_id)
1413
elif hook_status == 'delete':
1414
self.tt.unversion_file(trans_id)
1416
elif hook_status == 'done':
1417
# The hook function did whatever it needs to do directly, no
1418
# further action needed here.
1421
raise AssertionError('unknown hook_status: %r' % (hook_status,))
1422
if not self.this_tree.has_id(file_id) and result == "modified":
1423
self.tt.version_file(file_id, trans_id)
1424
# The merge has been performed, so the old contents should not be
1426
self.tt.delete_contents(trans_id)
1429
def _default_other_winner_merge(self, merge_hook_params):
1430
"""Replace this contents with other."""
1431
file_id = merge_hook_params.file_id
1432
trans_id = merge_hook_params.trans_id
1433
file_in_this = self.this_tree.has_id(file_id)
1434
if self.other_tree.has_id(file_id):
1435
# OTHER changed the file
1437
if wt.supports_content_filtering():
1438
# We get the path from the working tree if it exists.
1439
# That fails though when OTHER is adding a file, so
1440
# we fall back to the other tree to find the path if
1441
# it doesn't exist locally.
1443
filter_tree_path = wt.id2path(file_id)
1444
except errors.NoSuchId:
1445
filter_tree_path = self.other_tree.id2path(file_id)
1447
# Skip the id2path lookup for older formats
1448
filter_tree_path = None
1449
transform.create_from_tree(self.tt, trans_id,
1450
self.other_tree, file_id,
1451
filter_tree_path=filter_tree_path)
1454
# OTHER deleted the file
1455
return 'delete', None
1457
raise AssertionError(
1458
'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1461
def merge_contents(self, merge_hook_params):
1462
"""Fallback merge logic after user installed hooks."""
1463
# This function is used in merge hooks as the fallback instance.
1464
# Perhaps making this function and the functions it calls be a
1465
# a separate class would be better.
1466
if merge_hook_params.winner == 'other':
1143
1467
# OTHER is a straight winner, so replace this contents with other
1144
file_in_this = file_id in self.this_tree
1146
# Remove any existing contents
1147
self.tt.delete_contents(trans_id)
1148
if file_id in self.other_tree:
1149
# OTHER changed the file
1150
create_from_tree(self.tt, trans_id,
1151
self.other_tree, file_id)
1152
if not file_in_this:
1153
self.tt.version_file(file_id, trans_id)
1156
# OTHER deleted the file
1157
self.tt.unversion_file(trans_id)
1468
return self._default_other_winner_merge(merge_hook_params)
1469
elif merge_hook_params.is_file_merge():
1470
# THIS and OTHER are both files, so text merge. Either
1471
# BASE is a file, or both converted to files, so at least we
1472
# have agreement that output should be a file.
1474
self.text_merge(merge_hook_params.file_id,
1475
merge_hook_params.trans_id)
1476
except errors.BinaryFile:
1477
return 'not_applicable', None
1160
# We have a hypothetical conflict, but if we have files, then we
1161
# can try to merge the content
1162
if this_pair[0] == 'file' and other_pair[0] == 'file':
1163
# THIS and OTHER are both files, so text merge. Either
1164
# BASE is a file, or both converted to files, so at least we
1165
# have agreement that output should be a file.
1167
self.text_merge(file_id, trans_id)
1169
return contents_conflict()
1170
if file_id not in self.this_tree:
1171
self.tt.version_file(file_id, trans_id)
1173
self.tt.tree_kind(trans_id)
1174
self.tt.delete_contents(trans_id)
1179
return contents_conflict()
1480
return 'not_applicable', None
1181
1482
def get_lines(self, tree, file_id):
1182
1483
"""Return the lines in a file, or an empty list."""
1184
return tree.get_file(file_id).readlines()
1484
if tree.has_id(file_id):
1485
return tree.get_file_lines(file_id)
1306
1619
def cook_conflicts(self, fs_conflicts):
1307
1620
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1308
from conflicts import Conflict
1310
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1311
fp = FinalPaths(self.tt)
1621
content_conflict_file_ids = set()
1622
cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
1623
fp = transform.FinalPaths(self.tt)
1312
1624
for conflict in self._raw_conflicts:
1313
1625
conflict_type = conflict[0]
1314
if conflict_type in ('name conflict', 'parent conflict'):
1315
trans_id = conflict[1]
1316
conflict_args = conflict[2:]
1317
if trans_id not in name_conflicts:
1318
name_conflicts[trans_id] = {}
1319
unique_add(name_conflicts[trans_id], conflict_type,
1321
if conflict_type == 'contents conflict':
1626
if conflict_type == 'path conflict':
1628
this_parent, this_name,
1629
other_parent, other_name) = conflict[1:]
1630
if this_parent is None or this_name is None:
1631
this_path = '<deleted>'
1633
parent_path = fp.get_path(
1634
self.tt.trans_id_file_id(this_parent))
1635
this_path = osutils.pathjoin(parent_path, this_name)
1636
if other_parent is None or other_name is None:
1637
other_path = '<deleted>'
1639
if other_parent == self.other_tree.get_root_id():
1640
# The tree transform doesn't know about the other root,
1641
# so we special case here to avoid a NoFinalPath
1645
parent_path = fp.get_path(
1646
self.tt.trans_id_file_id(other_parent))
1647
other_path = osutils.pathjoin(parent_path, other_name)
1648
c = _mod_conflicts.Conflict.factory(
1649
'path conflict', path=this_path,
1650
conflict_path=other_path,
1652
elif conflict_type == 'contents conflict':
1322
1653
for trans_id in conflict[1]:
1323
1654
file_id = self.tt.final_file_id(trans_id)
1324
1655
if file_id is not None:
1328
1659
if path.endswith(suffix):
1329
1660
path = path[:-len(suffix)]
1331
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1332
self.cooked_conflicts.append(c)
1333
if conflict_type == 'text conflict':
1662
c = _mod_conflicts.Conflict.factory(conflict_type,
1663
path=path, file_id=file_id)
1664
content_conflict_file_ids.add(file_id)
1665
elif conflict_type == 'text conflict':
1334
1666
trans_id = conflict[1]
1335
1667
path = fp.get_path(trans_id)
1336
1668
file_id = self.tt.final_file_id(trans_id)
1337
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1338
self.cooked_conflicts.append(c)
1669
c = _mod_conflicts.Conflict.factory(conflict_type,
1670
path=path, file_id=file_id)
1672
raise AssertionError('bad conflict type: %r' % (conflict,))
1673
cooked_conflicts.append(c)
1340
for trans_id, conflicts in name_conflicts.iteritems():
1342
this_parent, other_parent = conflicts['parent conflict']
1343
if this_parent == other_parent:
1344
raise AssertionError()
1346
this_parent = other_parent = \
1347
self.tt.final_file_id(self.tt.final_parent(trans_id))
1349
this_name, other_name = conflicts['name conflict']
1350
if this_name == other_name:
1351
raise AssertionError()
1353
this_name = other_name = self.tt.final_name(trans_id)
1354
other_path = fp.get_path(trans_id)
1355
if this_parent is not None and this_name is not None:
1356
this_parent_path = \
1357
fp.get_path(self.tt.trans_id_file_id(this_parent))
1358
this_path = pathjoin(this_parent_path, this_name)
1360
this_path = "<deleted>"
1361
file_id = self.tt.final_file_id(trans_id)
1362
c = Conflict.factory('path conflict', path=this_path,
1363
conflict_path=other_path, file_id=file_id)
1675
self.cooked_conflicts = []
1676
# We want to get rid of path conflicts when a corresponding contents
1677
# conflict exists. This can occur when one branch deletes a file while
1678
# the other renames *and* modifies it. In this case, the content
1679
# conflict is enough.
1680
for c in cooked_conflicts:
1681
if (c.typestring == 'path conflict'
1682
and c.file_id in content_conflict_file_ids):
1364
1684
self.cooked_conflicts.append(c)
1365
self.cooked_conflicts.sort(key=Conflict.sort_key)
1685
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1368
1688
class WeaveMerger(Merge3Merger):
1372
1692
supports_reverse_cherrypick = False
1373
1693
history_based = True
1375
def _merged_lines(self, file_id):
1376
"""Generate the merged lines.
1377
There is no distinction between lines that are meant to contain <<<<<<<
1381
base = self.base_tree
1384
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1695
def _generate_merge_plan(self, file_id, base):
1696
return self.this_tree.plan_file_merge(file_id, self.other_tree,
1699
def _merged_lines(self, file_id):
1700
"""Generate the merged lines.
1701
There is no distinction between lines that are meant to contain <<<<<<<
1705
base = self.base_tree
1708
plan = self._generate_merge_plan(file_id, base)
1386
1709
if 'merge' in debug.debug_flags:
1387
1710
plan = list(plan)
1388
1711
trans_id = self.tt.trans_id_file_id(file_id)
1389
1712
name = self.tt.final_name(trans_id) + '.plan'
1390
contents = ('%10s|%s' % l for l in plan)
1713
contents = ('%11s|%s' % l for l in plan)
1391
1714
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1392
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1393
'>>>>>>> MERGE-SOURCE\n')
1394
return textmerge.merge_lines(self.reprocess)
1715
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1716
'>>>>>>> MERGE-SOURCE\n')
1717
lines, conflicts = textmerge.merge_lines(self.reprocess)
1719
base_lines = textmerge.base_from_plan()
1722
return lines, base_lines
1396
1724
def text_merge(self, file_id, trans_id):
1397
1725
"""Perform a (weave) text merge for a given file and file-id.
1398
1726
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1399
1727
and a conflict will be noted.
1401
lines, conflicts = self._merged_lines(file_id)
1729
lines, base_lines = self._merged_lines(file_id)
1402
1730
lines = list(lines)
1403
1731
# Note we're checking whether the OUTPUT is binary in this case,
1404
1732
# because we don't want to get into weave merge guts.
1405
check_text_lines(lines)
1733
textfile.check_text_lines(lines)
1406
1734
self.tt.create_file(lines, trans_id)
1735
if base_lines is not None:
1408
1737
self._raw_conflicts.append(('text conflict', trans_id))
1409
1738
name = self.tt.final_name(trans_id)
1410
1739
parent_id = self.tt.final_parent(trans_id)
1411
1740
file_group = self._dump_conflicts(name, parent_id, file_id,
1742
base_lines=base_lines)
1413
1743
file_group.append(trans_id)
1416
1746
class LCAMerger(WeaveMerger):
1418
def _merged_lines(self, file_id):
1419
"""Generate the merged lines.
1420
There is no distinction between lines that are meant to contain <<<<<<<
1424
base = self.base_tree
1427
plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1748
def _generate_merge_plan(self, file_id, base):
1749
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1429
if 'merge' in debug.debug_flags:
1431
trans_id = self.tt.trans_id_file_id(file_id)
1432
name = self.tt.final_name(trans_id) + '.plan'
1433
contents = ('%10s|%s' % l for l in plan)
1434
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1435
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1436
'>>>>>>> MERGE-SOURCE\n')
1437
return textmerge.merge_lines(self.reprocess)
1440
1752
class Diff3Merger(Merge3Merger):
1441
1753
"""Three-way merger using external diff3 for text merging"""
1443
1755
def dump_file(self, temp_dir, name, tree, file_id):
1444
out_path = pathjoin(temp_dir, name)
1756
out_path = osutils.pathjoin(temp_dir, name)
1445
1757
out_file = open(out_path, "wb")
1447
1759
in_file = tree.get_file(file_id)
1480
1792
osutils.rmtree(temp_dir)
1795
class PathNotInTree(errors.BzrError):
1797
_fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
1799
def __init__(self, path, tree):
1800
errors.BzrError.__init__(self, path=path, tree=tree)
1803
class MergeIntoMerger(Merger):
1804
"""Merger that understands other_tree will be merged into a subdir.
1806
This also changes the Merger api so that it uses real Branch, revision_id,
1807
and RevisonTree objects, rather than using revision specs.
1810
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1811
source_subpath, other_rev_id=None):
1812
"""Create a new MergeIntoMerger object.
1814
source_subpath in other_tree will be effectively copied to
1815
target_subdir in this_tree.
1817
:param this_tree: The tree that we will be merging into.
1818
:param other_branch: The Branch we will be merging from.
1819
:param other_tree: The RevisionTree object we want to merge.
1820
:param target_subdir: The relative path where we want to merge
1821
other_tree into this_tree
1822
:param source_subpath: The relative path specifying the subtree of
1823
other_tree to merge into this_tree.
1825
# It is assumed that we are merging a tree that is not in our current
1826
# ancestry, which means we are using the "EmptyTree" as our basis.
1827
null_ancestor_tree = this_tree.branch.repository.revision_tree(
1828
_mod_revision.NULL_REVISION)
1829
super(MergeIntoMerger, self).__init__(
1830
this_branch=this_tree.branch,
1831
this_tree=this_tree,
1832
other_tree=other_tree,
1833
base_tree=null_ancestor_tree,
1835
self._target_subdir = target_subdir
1836
self._source_subpath = source_subpath
1837
self.other_branch = other_branch
1838
if other_rev_id is None:
1839
other_rev_id = other_tree.get_revision_id()
1840
self.other_rev_id = self.other_basis = other_rev_id
1841
self.base_is_ancestor = True
1842
self.backup_files = True
1843
self.merge_type = Merge3Merger
1844
self.show_base = False
1845
self.reprocess = False
1846
self.interesting_ids = None
1847
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1848
target_subdir=self._target_subdir,
1849
source_subpath=self._source_subpath)
1850
if self._source_subpath != '':
1851
# If this isn't a partial merge make sure the revisions will be
1853
self._maybe_fetch(self.other_branch, self.this_branch,
1856
def set_pending(self):
1857
if self._source_subpath != '':
1859
Merger.set_pending(self)
1862
class _MergeTypeParameterizer(object):
1863
"""Wrap a merge-type class to provide extra parameters.
1865
This is hack used by MergeIntoMerger to pass some extra parameters to its
1866
merge_type. Merger.do_merge() sets up its own set of parameters to pass to
1867
the 'merge_type' member. It is difficult override do_merge without
1868
re-writing the whole thing, so instead we create a wrapper which will pass
1869
the extra parameters.
1872
def __init__(self, merge_type, **kwargs):
1873
self._extra_kwargs = kwargs
1874
self._merge_type = merge_type
1876
def __call__(self, *args, **kwargs):
1877
kwargs.update(self._extra_kwargs)
1878
return self._merge_type(*args, **kwargs)
1880
def __getattr__(self, name):
1881
return getattr(self._merge_type, name)
1884
class MergeIntoMergeType(Merge3Merger):
1885
"""Merger that incorporates a tree (or part of a tree) into another."""
1887
def __init__(self, *args, **kwargs):
1888
"""Initialize the merger object.
1890
:param args: See Merge3Merger.__init__'s args.
1891
:param kwargs: See Merge3Merger.__init__'s keyword args, except for
1892
source_subpath and target_subdir.
1893
:keyword source_subpath: The relative path specifying the subtree of
1894
other_tree to merge into this_tree.
1895
:keyword target_subdir: The relative path where we want to merge
1896
other_tree into this_tree
1898
# All of the interesting work happens during Merge3Merger.__init__(),
1899
# so we have have to hack in to get our extra parameters set.
1900
self._source_subpath = kwargs.pop('source_subpath')
1901
self._target_subdir = kwargs.pop('target_subdir')
1902
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1904
def _compute_transform(self):
1905
child_pb = ui.ui_factory.nested_progress_bar()
1907
entries = self._entries_to_incorporate()
1908
entries = list(entries)
1909
for num, (entry, parent_id) in enumerate(entries):
1910
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1911
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1912
trans_id = transform.new_by_entry(self.tt, entry,
1913
parent_trans_id, self.other_tree)
1916
self._finish_computing_transform()
1918
def _entries_to_incorporate(self):
1919
"""Yields pairs of (inventory_entry, new_parent)."""
1920
other_inv = self.other_tree.inventory
1921
subdir_id = other_inv.path2id(self._source_subpath)
1922
if subdir_id is None:
1923
# XXX: The error would be clearer if it gave the URL of the source
1924
# branch, but we don't have a reference to that here.
1925
raise PathNotInTree(self._source_subpath, "Source tree")
1926
subdir = other_inv[subdir_id]
1927
parent_in_target = osutils.dirname(self._target_subdir)
1928
target_id = self.this_tree.inventory.path2id(parent_in_target)
1929
if target_id is None:
1930
raise PathNotInTree(self._target_subdir, "Target tree")
1931
name_in_target = osutils.basename(self._target_subdir)
1932
merge_into_root = subdir.copy()
1933
merge_into_root.name = name_in_target
1934
if self.this_tree.inventory.has_id(merge_into_root.file_id):
1935
# Give the root a new file-id.
1936
# This can happen fairly easily if the directory we are
1937
# incorporating is the root, and both trees have 'TREE_ROOT' as
1938
# their root_id. Users will expect this to Just Work, so we
1939
# change the file-id here.
1940
# Non-root file-ids could potentially conflict too. That's really
1941
# an edge case, so we don't do anything special for those. We let
1942
# them cause conflicts.
1943
merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1944
yield (merge_into_root, target_id)
1945
if subdir.kind != 'directory':
1946
# No children, so we are done.
1948
for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1949
parent_id = entry.parent_id
1950
if parent_id == subdir.file_id:
1951
# The root's parent ID has changed, so make sure children of
1952
# the root refer to the new ID.
1953
parent_id = merge_into_root.file_id
1954
yield (entry, parent_id)
1483
1957
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1484
1958
backup_files=False,
1485
1959
merge_type=Merge3Merger,