45
46
from bzrlib.textfile import check_text_lines
46
47
from bzrlib.trace import mutter, warning, note
47
48
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
FinalPaths, create_by_entry, unique_add,
49
conflict_pass, FinalPaths, create_by_entry,
50
unique_add, ROOT_PARENT)
50
51
from bzrlib.versionedfile import WeaveMerge
51
52
from bzrlib import ui
103
104
object.__init__(self)
104
105
assert this_tree is not None, "this_tree is required"
105
106
self.this_branch = this_branch
106
self.this_basis = this_branch.last_revision()
107
self.this_basis = _mod_revision.ensure_null(
108
this_branch.last_revision())
107
109
self.this_rev_id = None
108
110
self.this_tree = this_tree
109
111
self.this_revision_tree = None
168
171
self.this_rev_id = self.this_basis
170
173
def set_interesting_files(self, file_list):
172
self._set_interesting_files(file_list)
173
except NotVersionedError, e:
174
raise BzrCommandError("%s is not a source file in any"
174
self.interesting_files = file_list
177
176
def _set_interesting_files(self, file_list):
178
177
"""Set the list of interesting ids from a list of files."""
217
216
self.other_branch, self.other_tree = _get_tree(other_revision,
218
217
self.this_branch)
219
218
if other_revision[1] == -1:
220
self.other_rev_id = self.other_branch.last_revision()
221
if self.other_rev_id is None:
219
self.other_rev_id = _mod_revision.ensure_null(
220
self.other_branch.last_revision())
221
if _mod_revision.is_null(self.other_rev_id):
222
222
raise NoCommits(self.other_branch)
223
223
self.other_basis = self.other_rev_id
224
224
elif other_revision[1] is not None:
281
281
if base_revision[1] == -1:
282
282
self.base_rev_id = base_branch.last_revision()
283
283
elif base_revision[1] is None:
284
self.base_rev_id = None
284
self.base_rev_id = _mod_revision.NULL_REVISION
286
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
286
self.base_rev_id = _mod_revision.ensure_null(
287
base_branch.get_rev_id(base_revision[1]))
287
288
if self.this_branch.base != base_branch.base:
288
289
self.this_branch.fetch(base_branch)
289
290
self.base_is_ancestor = is_ancestor(self.this_basis,
294
295
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
295
296
'other_tree': self.other_tree,
296
297
'interesting_ids': self.interesting_ids,
298
'interesting_files': self.interesting_files,
298
300
if self.merge_type.requires_base:
299
301
kwargs['base_tree'] = self.base_tree
423
425
supports_reprocess = True
424
426
supports_show_base = True
425
427
history_based = False
428
winner_idx = {"this": 2, "other": 1, "conflict": 1}
427
430
def __init__(self, working_tree, this_tree, base_tree, other_tree,
428
431
interesting_ids=None, reprocess=False, show_base=False,
429
pb=DummyProgress(), pp=None, change_reporter=None):
430
"""Initialize the merger object and perform the merge."""
432
pb=DummyProgress(), pp=None, change_reporter=None,
433
interesting_files=None):
434
"""Initialize the merger object and perform the merge.
436
:param working_tree: The working tree to apply the merge to
437
:param this_tree: The local tree in the merge operation
438
:param base_tree: The common tree in the merge operation
439
:param other_tree: The other other tree to merge changes from
440
:param interesting_ids: The file_ids of files that should be
441
participate in the merge. May not be combined with
443
:param: reprocess If True, perform conflict-reduction processing.
444
:param show_base: If True, show the base revision in text conflicts.
445
(incompatible with reprocess)
446
:param pb: A Progress bar
447
:param pp: A ProgressPhase object
448
:param change_reporter: An object that should report changes made
449
:param interesting_files: The tree-relative paths of files that should
450
participate in the merge. If these paths refer to directories,
451
the contents of those directories will also be included. May not
452
be combined with interesting_ids. If neither interesting_files nor
453
interesting_ids is specified, all files may participate in the
431
456
object.__init__(self)
457
if interesting_files is not None:
458
assert interesting_ids is None
459
self.interesting_ids = interesting_ids
460
self.interesting_files = interesting_files
432
461
self.this_tree = working_tree
433
462
self.this_tree.lock_tree_write()
434
463
self.base_tree = base_tree
445
474
if self.pp is None:
446
475
self.pp = ProgressPhase("Merge phase", 3, self.pb)
448
if interesting_ids is not None:
449
all_ids = interesting_ids
451
all_ids = set(base_tree)
452
all_ids.update(other_tree)
453
477
self.tt = TreeTransform(working_tree, self.pb)
455
479
self.pp.next_phase()
480
entries = self._entries3()
456
481
child_pb = ui.ui_factory.nested_progress_bar()
458
for num, file_id in enumerate(all_ids):
459
child_pb.update('Preparing file merge', num, len(all_ids))
460
self.merge_names(file_id)
461
file_status = self.merge_contents(file_id)
462
self.merge_executable(file_id, file_status)
483
for num, (file_id, changed, parents3, names3,
484
executable3) in enumerate(entries):
485
child_pb.update('Preparing file merge', num, len(entries))
486
self._merge_names(file_id, parents3, names3)
488
file_status = self.merge_contents(file_id)
490
file_status = 'unmodified'
491
self._merge_executable(file_id,
492
executable3, file_status)
464
494
child_pb.finished()
466
496
self.pp.next_phase()
467
497
child_pb = ui.ui_factory.nested_progress_bar()
469
fs_conflicts = resolve_conflicts(self.tt, child_pb)
499
fs_conflicts = resolve_conflicts(self.tt, child_pb,
500
lambda t, c: conflict_pass(t, c, self.other_tree))
471
502
child_pb.finished()
472
503
if change_reporter is not None:
489
520
self.this_tree.unlock()
524
"""Gather data about files modified between three trees.
526
Return a list of tuples of file_id, changed, parents3, names3,
527
executable3. changed is a boolean indicating whether the file contents
528
or kind were changed. parents3 is a tuple of parent ids for base,
529
other and this. names3 is a tuple of names for base, other and this.
530
executable3 is a tuple of execute-bit values for base, other and this.
533
iterator = self.other_tree._iter_changes(self.base_tree,
534
include_unchanged=True, specific_files=self.interesting_files,
535
extra_trees=[self.this_tree])
536
for (file_id, paths, changed, versioned, parents, names, kind,
537
executable) in iterator:
538
if (self.interesting_ids is not None and
539
file_id not in self.interesting_ids):
541
if file_id in self.this_tree.inventory:
542
entry = self.this_tree.inventory[file_id]
543
this_name = entry.name
544
this_parent = entry.parent_id
545
this_executable = entry.executable
549
this_executable = None
550
parents3 = parents + (this_parent,)
551
names3 = names + (this_name,)
552
executable3 = executable + (this_executable,)
553
result.append((file_id, changed, parents3, names3, executable3))
492
556
def fix_root(self):
494
558
self.tt.final_kind(self.tt.root)
566
630
return tree.kind(file_id)
633
def _three_way(base, other, this):
634
#if base == other, either they all agree, or only THIS has changed.
637
elif this not in (base, other):
639
# "Ambiguous clean merge" -- both sides have made the same change.
642
# this == base: only other has changed.
569
647
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
570
648
"""Do a three-way test on a scalar.
571
649
Return "this", "other" or "conflict", depending whether a value wins.
595
672
this_entry = get_entry(self.this_tree)
596
673
other_entry = get_entry(self.other_tree)
597
674
base_entry = get_entry(self.base_tree)
598
name_winner = self.scalar_three_way(this_entry, base_entry,
599
other_entry, file_id, self.name)
600
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
601
other_entry, file_id,
603
if this_entry is None:
675
entries = (base_entry, other_entry, this_entry)
678
for entry in entries:
683
names.append(entry.name)
684
parents.append(entry.parent_id)
685
return self._merge_names(file_id, parents, names)
687
def _merge_names(self, file_id, parents, names):
688
"""Perform a merge on file_id names and parents"""
689
base_name, other_name, this_name = names
690
base_parent, other_parent, this_parent = parents
692
name_winner = self._three_way(*names)
694
parent_id_winner = self._three_way(*parents)
695
if this_name is None:
604
696
if name_winner == "this":
605
697
name_winner = "other"
606
698
if parent_id_winner == "this":
610
702
if name_winner == "conflict":
611
703
trans_id = self.tt.trans_id_file_id(file_id)
612
704
self._raw_conflicts.append(('name conflict', trans_id,
613
self.name(this_entry, file_id),
614
self.name(other_entry, file_id)))
705
this_name, other_name))
615
706
if parent_id_winner == "conflict":
616
707
trans_id = self.tt.trans_id_file_id(file_id)
617
708
self._raw_conflicts.append(('parent conflict', trans_id,
618
self.parent(this_entry, file_id),
619
self.parent(other_entry, file_id)))
620
if other_entry is None:
709
this_parent, other_parent))
710
if other_name is None:
621
711
# it doesn't matter whether the result was 'other' or
622
712
# 'conflict'-- if there's no 'other', we leave it alone.
624
714
# if we get here, name_winner and parent_winner are set to safe values.
625
winner_entry = {"this": this_entry, "other": other_entry,
626
"conflict": other_entry}
627
715
trans_id = self.tt.trans_id_file_id(file_id)
628
parent_id = winner_entry[parent_id_winner].parent_id
716
parent_id = parents[self.winner_idx[parent_id_winner]]
629
717
if parent_id is not None:
630
718
parent_trans_id = self.tt.trans_id_file_id(parent_id)
631
self.tt.adjust_path(winner_entry[name_winner].name,
719
self.tt.adjust_path(names[self.winner_idx[name_winner]],
632
720
parent_trans_id, trans_id)
634
722
def merge_contents(self, file_id):
795
883
def merge_executable(self, file_id, file_status):
796
884
"""Perform a merge on the execute bit."""
885
executable = [self.executable(t, file_id) for t in (self.base_tree,
886
self.other_tree, self.this_tree)]
887
self._merge_executable(file_id, executable, file_status)
889
def _merge_executable(self, file_id, executable, file_status):
890
"""Perform a merge on the execute bit."""
891
base_executable, other_executable, this_executable = executable
797
892
if file_status == "deleted":
799
894
trans_id = self.tt.trans_id_file_id(file_id)
803
898
except NoSuchFile:
805
winner = self.scalar_three_way(self.this_tree, self.base_tree,
806
self.other_tree, file_id,
900
winner = self._three_way(*executable)
808
901
if winner == "conflict":
809
902
# There must be a None in here, if we have a conflict, but we
810
903
# need executability since file status was not deleted.
815
908
if winner == "this":
816
909
if file_status == "modified":
817
executability = self.this_tree.is_executable(file_id)
910
executability = this_executable
818
911
if executability is not None:
819
912
trans_id = self.tt.trans_id_file_id(file_id)
820
913
self.tt.set_executability(executability, trans_id)
822
915
assert winner == "other"
823
916
if file_id in self.other_tree:
824
executability = self.other_tree.is_executable(file_id)
917
executability = other_executable
825
918
elif file_id in self.this_tree:
826
executability = self.this_tree.is_executable(file_id)
919
executability = this_executable
827
920
elif file_id in self.base_tree:
828
executability = self.base_tree.is_executable(file_id)
921
executability = base_executable
829
922
if executability is not None:
830
923
trans_id = self.tt.trans_id_file_id(file_id)
831
924
self.tt.set_executability(executability, trans_id)
898
991
def __init__(self, working_tree, this_tree, base_tree, other_tree,
899
992
interesting_ids=None, pb=DummyProgress(), pp=None,
900
reprocess=False, change_reporter=None):
993
reprocess=False, change_reporter=None,
994
interesting_files=None):
901
995
self.this_revision_tree = self._get_revision_tree(this_tree)
902
996
self.other_revision_tree = self._get_revision_tree(other_tree)
903
997
super(WeaveMerger, self).__init__(working_tree, this_tree,
1031
1125
if interesting_files:
1032
1126
assert not interesting_ids, ('Only supply interesting_ids'
1033
1127
' or interesting_files')
1034
merger._set_interesting_files(interesting_files)
1128
merger.interesting_files = interesting_files
1035
1129
merger.show_base = show_base
1036
1130
merger.reprocess = reprocess
1037
1131
merger.other_rev_id = other_rev_id