57
57
def transform_tree(from_tree, to_tree, interesting_files=None):
58
58
with from_tree.lock_tree_write():
59
59
merge_inner(from_tree.branch, to_tree, from_tree,
60
ignore_zero=True, this_tree=from_tree,
61
interesting_files=interesting_files)
60
ignore_zero=True, this_tree=from_tree,
61
interesting_files=interesting_files)
64
64
class MergeHooks(hooks.Hooks):
66
66
def __init__(self):
67
67
hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
68
68
self.add_hook('merge_file_content',
69
"Called with a breezy.merge.Merger object to create a per file "
70
"merge object when starting a merge. "
71
"Should return either None or a subclass of "
72
"``breezy.merge.AbstractPerFileMerger``. "
73
"Such objects will then be called per file "
74
"that needs to be merged (including when one "
75
"side has deleted the file and the other has changed it). "
76
"See the AbstractPerFileMerger API docs for details on how it is "
69
"Called with a breezy.merge.Merger object to create a per file "
70
"merge object when starting a merge. "
71
"Should return either None or a subclass of "
72
"``breezy.merge.AbstractPerFileMerger``. "
73
"Such objects will then be called per file "
74
"that needs to be merged (including when one "
75
"side has deleted the file and the other has changed it). "
76
"See the AbstractPerFileMerger API docs for details on how it is "
79
79
self.add_hook('pre_merge',
80
'Called before a merge. '
81
'Receives a Merger object as the single argument.',
80
'Called before a merge. '
81
'Receives a Merger object as the single argument.',
83
83
self.add_hook('post_merge',
84
'Called after a merge. '
85
'Receives a Merger object as the single argument. '
86
'The return value is ignored.',
84
'Called after a merge. '
85
'Receives a Merger object as the single argument. '
86
'The return value is ignored.',
90
90
class AbstractPerFileMerger(object):
136
136
# THIS and OTHER aren't both files.
137
137
not params.is_file_merge() or
138
138
# The filename doesn't match
139
not self.file_matches(params)):
139
not self.file_matches(params)):
140
140
return 'not_applicable', None
141
141
return self.merge_matching(params)
369
369
if base_revision_id is not None:
370
370
if (base_revision_id != _mod_revision.NULL_REVISION and
371
371
revision_graph.is_ancestor(
372
base_revision_id, tree.branch.last_revision())):
372
base_revision_id, tree.branch.last_revision())):
373
373
base_revision_id = None
375
375
trace.warning('Performing cherrypick')
376
376
merger = klass.from_revision_ids(tree, other_revision_id,
377
base_revision_id, revision_graph=
377
base_revision_id, revision_graph=revision_graph)
379
378
return merger, verified
487
486
raise errors.NoCommits(self.other_branch)
488
487
if self.other_rev_id is not None:
489
488
self._cached_trees[self.other_rev_id] = self.other_tree
490
self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
489
self._maybe_fetch(self.other_branch,
490
self.this_branch, self.other_basis)
492
492
def set_other_revision(self, revision_id, other_branch):
493
493
"""Set 'other' based on a branch and revision id
530
530
self.base_rev_id = _mod_revision.NULL_REVISION
531
531
elif len(lcas) == 1:
532
532
self.base_rev_id = list(lcas)[0]
533
else: # len(lcas) > 1
533
else: # len(lcas) > 1
534
534
self._is_criss_cross = True
535
535
if len(lcas) > 2:
536
536
# find_unique_lca can only handle 2 nodes, so we have to
538
538
# the graph again, but better than re-implementing
539
539
# find_unique_lca.
540
540
self.base_rev_id = self.revision_graph.find_unique_lca(
541
revisions[0], revisions[1])
541
revisions[0], revisions[1])
543
543
self.base_rev_id = self.revision_graph.find_unique_lca(
545
545
sorted_lca_keys = self.revision_graph.find_merge_order(
546
546
revisions[0], lcas)
547
547
if self.base_rev_id == _mod_revision.NULL_REVISION:
560
560
interesting_revision_ids = set(lcas)
561
561
interesting_revision_ids.add(self.base_rev_id)
562
562
interesting_trees = dict((t.get_revision_id(), t)
563
for t in self.this_branch.repository.revision_trees(
564
interesting_revision_ids))
563
for t in self.this_branch.repository.revision_trees(
564
interesting_revision_ids))
565
565
self._cached_trees.update(interesting_trees)
566
566
if self.base_rev_id in lcas:
567
567
self.base_tree = interesting_trees[self.base_rev_id]
615
615
raise errors.BzrError("Showing base is not supported for this"
616
616
" merge type. %s" % self.merge_type)
617
617
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
618
and not self.base_is_other_ancestor):
618
and not self.base_is_other_ancestor):
619
619
raise errors.CannotReverseCherrypick()
620
620
if self.merge_type.supports_cherrypick:
621
621
kwargs['cherrypick'] = (not self.base_is_ancestor or
640
640
sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
641
641
other_revision = self.other_tree.get_reference_revision(
642
642
relpath, file_id)
643
if other_revision == sub_tree.last_revision():
643
if other_revision == sub_tree.last_revision():
645
645
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
646
646
sub_merge.merge_type = self.merge_type
806
807
# Try merging each entry
807
808
child_pb.update(gettext('Preparing file merge'),
808
809
num, len(entries))
809
self._merge_names(file_id, paths3, parents3, names3, resolver=resolver)
810
self._merge_names(file_id, paths3, parents3,
811
names3, resolver=resolver)
811
813
file_status = self._do_merge_contents(paths3, file_id)
813
815
file_status = 'unmodified'
814
816
self._merge_executable(paths3, file_id, executable3,
815
file_status, resolver=resolver)
817
file_status, resolver=resolver)
816
818
self.tt.fixup_new_roots()
817
819
self._finish_computing_transform()
824
826
with ui.ui_factory.nested_progress_bar() as child_pb:
825
827
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
826
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
828
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
827
829
if self.change_reporter is not None:
828
830
from breezy import delta
829
831
delta.report_changes(
845
847
iterator = self.other_tree.iter_changes(self.base_tree,
846
specific_files=self.interesting_files,
847
extra_trees=[self.this_tree])
848
specific_files=self.interesting_files,
849
extra_trees=[self.this_tree])
848
850
this_interesting_files = self.this_tree.find_related_paths_across_trees(
849
self.interesting_files, trees=[self.other_tree])
851
self.interesting_files, trees=[self.other_tree])
850
852
this_entries = dict(self.this_tree.iter_entries_by_dir(
851
853
specific_files=this_interesting_files))
852
854
for (file_id, paths, changed, versioned, parents, names, kind,
853
855
executable) in iterator:
854
856
if paths[0] is not None:
855
857
this_path = _mod_tree.find_previous_path(
856
self.base_tree, self.this_tree, paths[0])
858
self.base_tree, self.this_tree, paths[0])
858
860
this_path = _mod_tree.find_previous_path(
859
self.other_tree, self.this_tree, paths[1])
861
self.other_tree, self.this_tree, paths[1])
860
862
this_entry = this_entries.get(this_path)
861
863
if this_entry is not None:
862
864
this_name = this_entry.name
870
872
names3 = names + (this_name,)
871
873
paths3 = paths + (this_path, )
872
874
executable3 = executable + (this_executable,)
873
result.append((file_id, changed, paths3, parents3, names3, executable3))
875
result.append((file_id, changed, paths3,
876
parents3, names3, executable3))
876
879
def _entries_lca(self):
897
900
lookup_trees.extend(self._lca_trees)
898
901
# I think we should include the lca trees as well
899
902
interesting_files = self.other_tree.find_related_paths_across_trees(
900
self.interesting_files, lookup_trees)
903
self.interesting_files, lookup_trees)
902
905
interesting_files = None
1009
1012
(base_ie.executable, lca_executable),
1010
1013
other_ie.executable, this_ie.executable)
1011
1014
if (parent_id_winner == 'this' and name_winner == 'this'
1012
and sha1_winner == 'this' and exec_winner == 'this'):
1015
and sha1_winner == 'this' and exec_winner == 'this'):
1013
1016
# No kind, parent, name, exec, or content change for
1014
1017
# OTHER, so this node is not considered interesting
1020
1023
if ie.kind != 'symlink':
1022
1025
return tree.get_symlink_target(path, file_id)
1023
base_target = get_target(base_ie, self.base_tree, base_path)
1026
base_target = get_target(
1027
base_ie, self.base_tree, base_path)
1024
1028
lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1025
1029
in zip(lca_entries, self._lca_trees, lca_paths)]
1026
this_target = get_target(this_ie, self.this_tree, this_path)
1027
other_target = get_target(other_ie, self.other_tree, other_path)
1030
this_target = get_target(
1031
this_ie, self.this_tree, this_path)
1032
other_target = get_target(
1033
other_ie, self.other_tree, other_path)
1028
1034
target_winner = self._lca_multi_way(
1029
1035
(base_target, lca_targets),
1030
1036
other_target, this_target)
1031
1037
if (parent_id_winner == 'this' and name_winner == 'this'
1032
and target_winner == 'this'):
1038
and target_winner == 'this'):
1033
1039
# No kind, parent, name, or symlink target change
1034
1040
# not interesting
1056
1062
other_ie.name, this_ie.name),
1057
1063
((base_ie.executable, lca_executable),
1058
1064
other_ie.executable, this_ie.executable)
1062
1068
def write_modified(self, results):
1155
1161
base_val, lca_vals = bases
1156
1162
# Remove 'base_val' from the lca_vals, because it is not interesting
1157
1163
filtered_lca_vals = [lca_val for lca_val in lca_vals
1158
if lca_val != base_val]
1164
if lca_val != base_val]
1159
1165
if len(filtered_lca_vals) == 0:
1160
1166
return Merge3Merger._three_way(base_val, other, this)
1296
1302
trans_id = self.tt.trans_id_file_id(file_id)
1297
1303
params = MergeFileHookParams(
1298
1304
self, file_id, (base_path, other_path,
1299
this_path), trans_id, this_pair[0],
1305
this_path), trans_id, this_pair[0],
1300
1306
other_pair[0], winner)
1301
1307
hooks = self.active_hooks
1302
1308
hook_status = 'not_applicable'
1318
1324
parent_id = self.tt.final_parent(trans_id)
1319
1325
duplicate = False
1320
1326
inhibit_content_conflict = False
1321
if params.this_kind is None: # file_id is not in THIS
1327
if params.this_kind is None: # file_id is not in THIS
1322
1328
# Is the name used for a different file_id ?
1323
1329
if self.this_tree.is_versioned(other_path):
1324
1330
# Two entries for the same path
1331
1337
other_path, file_id=file_id,
1332
1338
filter_tree_path=self._get_filter_tree_path(file_id))
1333
1339
inhibit_content_conflict = True
1334
elif params.other_kind is None: # file_id is not in OTHER
1340
elif params.other_kind is None: # file_id is not in OTHER
1335
1341
# Is the name used for a different file_id ?
1336
1342
if self.other_tree.is_versioned(this_path):
1337
1343
# Two entries for the same path again, but here, the other
1348
1354
# This is a contents conflict, because none of the available
1349
1355
# functions could merge it.
1350
1356
file_group = self._dump_conflicts(
1351
name, (base_path, other_path, this_path), parent_id,
1352
file_id, set_version=True)
1357
name, (base_path, other_path, this_path), parent_id,
1358
file_id, set_version=True)
1353
1359
self._raw_conflicts.append(('contents conflict', file_group))
1354
1360
elif hook_status == 'success':
1355
1361
self.tt.create_file(lines, trans_id)
1451
1457
def iter_merge3(retval):
1452
1458
retval["text_conflicts"] = False
1453
for line in m3.merge_lines(name_a = b"TREE",
1454
name_b = b"MERGE-SOURCE",
1455
name_base = b"BASE-REVISION",
1459
for line in m3.merge_lines(name_a=b"TREE",
1460
name_b=b"MERGE-SOURCE",
1461
name_base=b"BASE-REVISION",
1456
1462
start_marker=start_marker,
1457
1463
base_marker=base_marker,
1458
1464
reprocess=self.reprocess):
1518
1524
for suffix, tree, path, lines in data:
1519
1525
if path is not None:
1520
1526
trans_id = self._conflict_file(
1521
name, parent_id, path, tree, file_id, suffix, lines,
1527
name, parent_id, path, tree, file_id, suffix, lines,
1523
1529
file_group.append(trans_id)
1524
1530
if set_version and not versioned:
1525
1531
self.tt.version_file(file_id, trans_id)
1532
1538
name = name + '.' + suffix
1533
1539
trans_id = self.tt.create_path(name, parent_id)
1534
1540
transform.create_from_tree(
1535
self.tt, trans_id, tree, path,
1536
file_id=file_id, chunks=lines,
1537
filter_tree_path=filter_tree_path)
1541
self.tt, trans_id, tree, path,
1542
file_id=file_id, chunks=lines,
1543
filter_tree_path=filter_tree_path)
1538
1544
return trans_id
1540
1546
def merge_executable(self, paths, file_id, file_status):
1541
1547
"""Perform a merge on the execute bit."""
1542
1548
executable = [self.executable(t, p, file_id) for t, p in zip([self.base_tree,
1543
self.other_tree, self.this_tree], paths)]
1549
self.other_tree, self.this_tree], paths)]
1544
1550
self._merge_executable(paths, file_id, executable, file_status,
1545
1551
resolver=self._three_way)
1554
1560
winner = resolver(*executable)
1555
1561
if winner == "conflict":
1556
# There must be a None in here, if we have a conflict, but we
1557
# need executability since file status was not deleted.
1562
# There must be a None in here, if we have a conflict, but we
1563
# need executability since file status was not deleted.
1558
1564
if other_path is None:
1559
1565
winner = "this"
1586
1592
conflict_type = conflict[0]
1587
1593
if conflict_type == 'path conflict':
1588
1594
(trans_id, file_id,
1589
this_parent, this_name,
1590
other_parent, other_name) = conflict[1:]
1595
this_parent, this_name,
1596
other_parent, other_name) = conflict[1:]
1591
1597
if this_parent is None or this_name is None:
1592
1598
this_path = '<deleted>'
1594
parent_path = fp.get_path(
1600
parent_path = fp.get_path(
1595
1601
self.tt.trans_id_file_id(this_parent))
1596
1602
this_path = osutils.pathjoin(parent_path, this_name)
1597
1603
if other_parent is None or other_name is None:
1604
1610
parent_path = ''
1606
parent_path = fp.get_path(
1612
parent_path = fp.get_path(
1607
1613
self.tt.trans_id_file_id(other_parent))
1608
1614
other_path = osutils.pathjoin(parent_path, other_name)
1609
1615
c = _mod_conflicts.Conflict.factory(
1642
1648
# conflict is enough.
1643
1649
for c in cooked_conflicts:
1644
1650
if (c.typestring == 'path conflict'
1645
and c.file_id in content_conflict_file_ids):
1651
and c.file_id in content_conflict_file_ids):
1647
1653
self.cooked_conflicts.append(c)
1648
1654
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1739
1746
temp_dir = osutils.mkdtemp(prefix="bzr-")
1741
1748
new_file = osutils.pathjoin(temp_dir, "new")
1742
this = self.dump_file(temp_dir, "this", self.this_tree, this_path, file_id)
1743
base = self.dump_file(temp_dir, "base", self.base_tree, base_path, file_id)
1744
other = self.dump_file(temp_dir, "other", self.other_tree, other_path, file_id)
1749
this = self.dump_file(
1750
temp_dir, "this", self.this_tree, this_path, file_id)
1751
base = self.dump_file(
1752
temp_dir, "base", self.base_tree, base_path, file_id)
1753
other = self.dump_file(
1754
temp_dir, "other", self.other_tree, other_path, file_id)
1745
1755
status = breezy.patch.diff3(new_file, this, base, other)
1746
1756
if status not in (0, 1):
1747
1757
raise errors.BzrError("Unhandled diff3 exit code")
1774
1784
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1775
source_subpath, other_rev_id=None):
1785
source_subpath, other_rev_id=None):
1776
1786
"""Create a new MergeIntoMerger object.
1778
1788
source_subpath in other_tree will be effectively copied to
1789
1799
# It is assumed that we are merging a tree that is not in our current
1790
1800
# ancestry, which means we are using the "EmptyTree" as our basis.
1791
1801
null_ancestor_tree = this_tree.branch.repository.revision_tree(
1792
_mod_revision.NULL_REVISION)
1802
_mod_revision.NULL_REVISION)
1793
1803
super(MergeIntoMerger, self).__init__(
1794
1804
this_branch=this_tree.branch,
1795
1805
this_tree=this_tree,
1809
1819
self.reprocess = False
1810
1820
self.interesting_files = None
1811
1821
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1812
target_subdir=self._target_subdir,
1813
source_subpath=self._source_subpath)
1822
target_subdir=self._target_subdir,
1823
source_subpath=self._source_subpath)
1814
1824
if self._source_subpath != '':
1815
1825
# If this isn't a partial merge make sure the revisions will be
1817
1827
self._maybe_fetch(self.other_branch, self.this_branch,
1820
1830
def set_pending(self):
1821
1831
if self._source_subpath != '':
1870
1880
entries = self._entries_to_incorporate()
1871
1881
entries = list(entries)
1872
1882
for num, (entry, parent_id, relpath) in enumerate(entries):
1873
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1883
child_pb.update(gettext('Preparing file merge'),
1874
1885
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1875
1886
path = osutils.pathjoin(self._source_subpath, relpath)
1876
1887
trans_id = transform.new_by_entry(path, self.tt, entry,
1877
parent_trans_id, self.other_tree)
1888
parent_trans_id, self.other_tree)
1878
1889
self._finish_computing_transform()
1880
1891
def _entries_to_incorporate(self):
2082
2093
yield 'killed-a', self.lines_b[b_index]
2083
2094
# handle common lines
2084
for a_index in range(i, i+n):
2095
for a_index in range(i, i + n):
2085
2096
yield 'unchanged', self.lines_a[a_index]
2089
2100
def _get_matching_blocks(self, left_revision, right_revision):
2090
2101
"""Return a description of which sections of two revisions match.
2146
2157
for i, j, n in matcher.get_matching_blocks():
2147
2158
for jj in range(last_j, j):
2148
2159
yield new_plan[jj]
2149
for jj in range(j, j+n):
2160
for jj in range(j, j + n):
2150
2161
plan_line = new_plan[jj]
2151
2162
if plan_line[0] == 'new-b':
2383
2394
are combined, they are written out in the format described in
2384
2395
VersionedFile.plan_merge
2386
if self._head_key is not None: # There was a single head
2397
if self._head_key is not None: # There was a single head
2387
2398
if self._head_key == self.a_key: