45
45
from bzrlib.textfile import check_text_lines
46
46
from bzrlib.trace import mutter, warning, note
47
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
FinalPaths, create_by_entry, unique_add,
48
conflict_pass, FinalPaths, create_by_entry,
49
unique_add, ROOT_PARENT)
50
50
from bzrlib.versionedfile import WeaveMerge
51
51
from bzrlib import ui
168
169
self.this_rev_id = self.this_basis
170
171
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"
172
self.interesting_files = file_list
177
174
def _set_interesting_files(self, file_list):
178
175
"""Set the list of interesting ids from a list of files."""
294
291
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
295
292
'other_tree': self.other_tree,
296
293
'interesting_ids': self.interesting_ids,
294
'interesting_files': self.interesting_files,
298
296
if self.merge_type.requires_base:
299
297
kwargs['base_tree'] = self.base_tree
423
421
supports_reprocess = True
424
422
supports_show_base = True
425
423
history_based = False
424
winner_idx = {"this": 2, "other": 1, "conflict": 1}
427
426
def __init__(self, working_tree, this_tree, base_tree, other_tree,
428
427
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."""
428
pb=DummyProgress(), pp=None, change_reporter=None,
429
interesting_files=None):
430
"""Initialize the merger object and perform the merge.
432
:param working_tree: The working tree to apply the merge to
433
:param this_tree: The local tree in the merge operation
434
:param base_tree: The common tree in the merge operation
435
:param other_tree: The other other tree to merge changes from
436
:param interesting_ids: The file_ids of files that should be
437
participate in the merge. May not be combined with
439
:param: reprocess If True, perform conflict-reduction processing.
440
:param show_base: If True, show the base revision in text conflicts.
441
(incompatible with reprocess)
442
:param pb: A Progress bar
443
:param pp: A ProgressPhase object
444
:param change_reporter: An object that should report changes made
445
:param interesting_files: The tree-relative paths of files that should
446
participate in the merge. If these paths refer to directories,
447
the contents of those directories will also be included. May not
448
be combined with interesting_ids. If neither interesting_files nor
449
interesting_ids is specified, all files may participate in the
431
452
object.__init__(self)
453
if interesting_files is not None:
454
assert interesting_ids is None
455
self.interesting_ids = interesting_ids
456
self.interesting_files = interesting_files
432
457
self.this_tree = working_tree
433
458
self.this_tree.lock_tree_write()
434
459
self.base_tree = base_tree
445
470
if self.pp is None:
446
471
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
473
self.tt = TreeTransform(working_tree, self.pb)
455
475
self.pp.next_phase()
476
entries = self._entries3()
456
477
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)
479
for num, (file_id, changed, parents3, names3,
480
executable3) in enumerate(entries):
481
child_pb.update('Preparing file merge', num, len(entries))
482
self._merge_names(file_id, parents3, names3)
484
file_status = self.merge_contents(file_id)
486
file_status = 'unmodified'
487
self._merge_executable(file_id,
488
executable3, file_status)
464
490
child_pb.finished()
466
492
self.pp.next_phase()
467
493
child_pb = ui.ui_factory.nested_progress_bar()
469
fs_conflicts = resolve_conflicts(self.tt, child_pb)
495
fs_conflicts = resolve_conflicts(self.tt, child_pb,
496
lambda t, c: conflict_pass(t, c, self.other_tree))
471
498
child_pb.finished()
472
499
if change_reporter is not None:
489
516
self.this_tree.unlock()
520
"""Gather data about files modified between three trees.
522
Return a list of tuples of file_id, changed, parents3, names3,
523
executable3. changed is a boolean indicating whether the file contents
524
or kind were changed. parents3 is a tuple of parent ids for base,
525
other and this. names3 is a tuple of names for base, other and this.
526
executable3 is a tuple of execute-bit values for base, other and this.
529
iterator = self.other_tree._iter_changes(self.base_tree,
530
include_unchanged=True, specific_files=self.interesting_files,
531
extra_trees=[self.this_tree])
532
for (file_id, paths, changed, versioned, parents, names, kind,
533
executable) in iterator:
534
if (self.interesting_ids is not None and
535
file_id not in self.interesting_ids):
537
if file_id in self.this_tree.inventory:
538
entry = self.this_tree.inventory[file_id]
539
this_name = entry.name
540
this_parent = entry.parent_id
541
this_executable = entry.executable
545
this_executable = None
546
parents3 = parents + (this_parent,)
547
names3 = names + (this_name,)
548
executable3 = executable + (this_executable,)
549
result.append((file_id, changed, parents3, names3, executable3))
492
552
def fix_root(self):
494
554
self.tt.final_kind(self.tt.root)
566
626
return tree.kind(file_id)
629
def _three_way(base, other, this):
630
#if base == other, either they all agree, or only THIS has changed.
633
elif this not in (base, other):
635
# "Ambiguous clean merge" -- both sides have made the same change.
638
# this == base: only other has changed.
569
643
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
570
644
"""Do a three-way test on a scalar.
571
645
Return "this", "other" or "conflict", depending whether a value wins.
595
668
this_entry = get_entry(self.this_tree)
596
669
other_entry = get_entry(self.other_tree)
597
670
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:
671
entries = (base_entry, other_entry, this_entry)
674
for entry in entries:
679
names.append(entry.name)
680
parents.append(entry.parent_id)
681
return self._merge_names(file_id, parents, names)
683
def _merge_names(self, file_id, parents, names):
684
"""Perform a merge on file_id names and parents"""
685
base_name, other_name, this_name = names
686
base_parent, other_parent, this_parent = parents
688
name_winner = self._three_way(*names)
690
parent_id_winner = self._three_way(*parents)
691
if this_name is None:
604
692
if name_winner == "this":
605
693
name_winner = "other"
606
694
if parent_id_winner == "this":
610
698
if name_winner == "conflict":
611
699
trans_id = self.tt.trans_id_file_id(file_id)
612
700
self._raw_conflicts.append(('name conflict', trans_id,
613
self.name(this_entry, file_id),
614
self.name(other_entry, file_id)))
701
this_name, other_name))
615
702
if parent_id_winner == "conflict":
616
703
trans_id = self.tt.trans_id_file_id(file_id)
617
704
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:
705
this_parent, other_parent))
706
if other_name is None:
621
707
# it doesn't matter whether the result was 'other' or
622
708
# 'conflict'-- if there's no 'other', we leave it alone.
624
710
# 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
711
trans_id = self.tt.trans_id_file_id(file_id)
628
parent_id = winner_entry[parent_id_winner].parent_id
712
parent_id = parents[self.winner_idx[parent_id_winner]]
629
713
if parent_id is not None:
630
714
parent_trans_id = self.tt.trans_id_file_id(parent_id)
631
self.tt.adjust_path(winner_entry[name_winner].name,
715
self.tt.adjust_path(names[self.winner_idx[name_winner]],
632
716
parent_trans_id, trans_id)
634
718
def merge_contents(self, file_id):
795
879
def merge_executable(self, file_id, file_status):
796
880
"""Perform a merge on the execute bit."""
881
executable = [self.executable(t, file_id) for t in (self.base_tree,
882
self.other_tree, self.this_tree)]
883
self._merge_executable(file_id, executable, file_status)
885
def _merge_executable(self, file_id, executable, file_status):
886
"""Perform a merge on the execute bit."""
887
base_executable, other_executable, this_executable = executable
797
888
if file_status == "deleted":
799
890
trans_id = self.tt.trans_id_file_id(file_id)
803
894
except NoSuchFile:
805
winner = self.scalar_three_way(self.this_tree, self.base_tree,
806
self.other_tree, file_id,
896
winner = self._three_way(*executable)
808
897
if winner == "conflict":
809
898
# There must be a None in here, if we have a conflict, but we
810
899
# need executability since file status was not deleted.
815
904
if winner == "this":
816
905
if file_status == "modified":
817
executability = self.this_tree.is_executable(file_id)
906
executability = this_executable
818
907
if executability is not None:
819
908
trans_id = self.tt.trans_id_file_id(file_id)
820
909
self.tt.set_executability(executability, trans_id)
822
911
assert winner == "other"
823
912
if file_id in self.other_tree:
824
executability = self.other_tree.is_executable(file_id)
913
executability = other_executable
825
914
elif file_id in self.this_tree:
826
executability = self.this_tree.is_executable(file_id)
915
executability = this_executable
827
916
elif file_id in self.base_tree:
828
executability = self.base_tree.is_executable(file_id)
917
executability = base_executable
829
918
if executability is not None:
830
919
trans_id = self.tt.trans_id_file_id(file_id)
831
920
self.tt.set_executability(executability, trans_id)
898
987
def __init__(self, working_tree, this_tree, base_tree, other_tree,
899
988
interesting_ids=None, pb=DummyProgress(), pp=None,
900
reprocess=False, change_reporter=None):
989
reprocess=False, change_reporter=None,
990
interesting_files=None):
901
991
self.this_revision_tree = self._get_revision_tree(this_tree)
902
992
self.other_revision_tree = self._get_revision_tree(other_tree)
903
993
super(WeaveMerger, self).__init__(working_tree, this_tree,
1031
1121
if interesting_files:
1032
1122
assert not interesting_ids, ('Only supply interesting_ids'
1033
1123
' or interesting_files')
1034
merger._set_interesting_files(interesting_files)
1124
merger.interesting_files = interesting_files
1035
1125
merger.show_base = show_base
1036
1126
merger.reprocess = reprocess
1037
1127
merger.other_rev_id = other_rev_id