60
57
def transform_tree(from_tree, to_tree, interesting_files=None):
61
58
with from_tree.lock_tree_write():
62
59
merge_inner(from_tree.branch, to_tree, from_tree,
63
ignore_zero=True, this_tree=from_tree,
64
interesting_files=interesting_files)
60
ignore_zero=True, this_tree=from_tree,
61
interesting_files=interesting_files)
67
64
class MergeHooks(hooks.Hooks):
69
66
def __init__(self):
70
67
hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
71
68
self.add_hook('merge_file_content',
72
"Called with a breezy.merge.Merger object to create a per file "
73
"merge object when starting a merge. "
74
"Should return either None or a subclass of "
75
"``breezy.merge.AbstractPerFileMerger``. "
76
"Such objects will then be called per file "
77
"that needs to be merged (including when one "
78
"side has deleted the file and the other has changed it). "
79
"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 "
82
79
self.add_hook('pre_merge',
83
'Called before a merge. '
84
'Receives a Merger object as the single argument.',
80
'Called before a merge. '
81
'Receives a Merger object as the single argument.',
86
83
self.add_hook('post_merge',
87
'Called after a merge. '
88
'Receives a Merger object as the single argument. '
89
'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.',
93
90
class AbstractPerFileMerger(object):
139
136
# THIS and OTHER aren't both files.
140
137
not params.is_file_merge() or
141
138
# The filename doesn't match
142
not self.file_matches(params)):
139
not self.file_matches(params)):
143
140
return 'not_applicable', None
144
141
return self.merge_matching(params)
372
369
if base_revision_id is not None:
373
370
if (base_revision_id != _mod_revision.NULL_REVISION and
374
371
revision_graph.is_ancestor(
375
base_revision_id, tree.branch.last_revision())):
372
base_revision_id, tree.branch.last_revision())):
376
373
base_revision_id = None
378
375
trace.warning('Performing cherrypick')
379
376
merger = klass.from_revision_ids(tree, other_revision_id,
380
base_revision_id, revision_graph=
377
base_revision_id, revision_graph=revision_graph)
382
378
return merger, verified
490
486
raise errors.NoCommits(self.other_branch)
491
487
if self.other_rev_id is not None:
492
488
self._cached_trees[self.other_rev_id] = self.other_tree
493
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)
495
492
def set_other_revision(self, revision_id, other_branch):
496
493
"""Set 'other' based on a branch and revision id
533
530
self.base_rev_id = _mod_revision.NULL_REVISION
534
531
elif len(lcas) == 1:
535
532
self.base_rev_id = list(lcas)[0]
536
else: # len(lcas) > 1
533
else: # len(lcas) > 1
537
534
self._is_criss_cross = True
538
535
if len(lcas) > 2:
539
536
# find_unique_lca can only handle 2 nodes, so we have to
541
538
# the graph again, but better than re-implementing
542
539
# find_unique_lca.
543
540
self.base_rev_id = self.revision_graph.find_unique_lca(
544
revisions[0], revisions[1])
541
revisions[0], revisions[1])
546
543
self.base_rev_id = self.revision_graph.find_unique_lca(
548
545
sorted_lca_keys = self.revision_graph.find_merge_order(
549
546
revisions[0], lcas)
550
547
if self.base_rev_id == _mod_revision.NULL_REVISION:
563
560
interesting_revision_ids = set(lcas)
564
561
interesting_revision_ids.add(self.base_rev_id)
565
562
interesting_trees = dict((t.get_revision_id(), t)
566
for t in self.this_branch.repository.revision_trees(
567
interesting_revision_ids))
563
for t in self.this_branch.repository.revision_trees(
564
interesting_revision_ids))
568
565
self._cached_trees.update(interesting_trees)
569
566
if self.base_rev_id in lcas:
570
567
self.base_tree = interesting_trees[self.base_rev_id]
618
615
raise errors.BzrError("Showing base is not supported for this"
619
616
" merge type. %s" % self.merge_type)
620
617
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
621
and not self.base_is_other_ancestor):
618
and not self.base_is_other_ancestor):
622
619
raise errors.CannotReverseCherrypick()
623
620
if self.merge_type.supports_cherrypick:
624
621
kwargs['cherrypick'] = (not self.base_is_ancestor or
641
638
if self.recurse == 'down':
642
639
for relpath, file_id in self.this_tree.iter_references():
643
sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
640
sub_tree = self.this_tree.get_nested_tree(relpath)
644
641
other_revision = self.other_tree.get_reference_revision(
646
if other_revision == sub_tree.last_revision():
643
if other_revision == sub_tree.last_revision():
648
645
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
649
646
sub_merge.merge_type = self.merge_type
653
650
base_tree_path = _mod_tree.find_previous_path(
654
651
self.this_tree, self.base_tree, relpath)
655
652
base_revision = self.base_tree.get_reference_revision(
656
base_tree_path, file_id)
657
654
sub_merge.base_tree = \
658
655
sub_tree.branch.repository.revision_tree(base_revision)
659
656
sub_merge.base_rev_id = base_revision
809
807
# Try merging each entry
810
808
child_pb.update(gettext('Preparing file merge'),
811
809
num, len(entries))
812
self._merge_names(file_id, paths3, parents3, names3, resolver=resolver)
810
self._merge_names(file_id, paths3, parents3,
811
names3, resolver=resolver)
814
813
file_status = self._do_merge_contents(paths3, file_id)
816
815
file_status = 'unmodified'
817
816
self._merge_executable(paths3, file_id, executable3,
818
file_status, resolver=resolver)
817
file_status, resolver=resolver)
819
818
self.tt.fixup_new_roots()
820
819
self._finish_computing_transform()
827
826
with ui.ui_factory.nested_progress_bar() as child_pb:
828
827
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
829
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
828
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
830
829
if self.change_reporter is not None:
831
830
from breezy import delta
832
831
delta.report_changes(
848
847
iterator = self.other_tree.iter_changes(self.base_tree,
849
specific_files=self.interesting_files,
850
extra_trees=[self.this_tree])
848
specific_files=self.interesting_files,
849
extra_trees=[self.this_tree])
851
850
this_interesting_files = self.this_tree.find_related_paths_across_trees(
852
self.interesting_files, trees=[self.other_tree])
851
self.interesting_files, trees=[self.other_tree])
853
852
this_entries = dict(self.this_tree.iter_entries_by_dir(
854
853
specific_files=this_interesting_files))
855
854
for (file_id, paths, changed, versioned, parents, names, kind,
856
855
executable) in iterator:
857
856
if paths[0] is not None:
858
857
this_path = _mod_tree.find_previous_path(
859
self.base_tree, self.this_tree, paths[0])
858
self.base_tree, self.this_tree, paths[0])
861
860
this_path = _mod_tree.find_previous_path(
862
self.other_tree, self.this_tree, paths[1])
861
self.other_tree, self.this_tree, paths[1])
863
862
this_entry = this_entries.get(this_path)
864
863
if this_entry is not None:
865
864
this_name = this_entry.name
873
872
names3 = names + (this_name,)
874
873
paths3 = paths + (this_path, )
875
874
executable3 = executable + (this_executable,)
876
result.append((file_id, changed, paths3, parents3, names3, executable3))
875
result.append((file_id, changed, paths3,
876
parents3, names3, executable3))
879
879
def _entries_lca(self):
900
900
lookup_trees.extend(self._lca_trees)
901
901
# I think we should include the lca trees as well
902
902
interesting_files = self.other_tree.find_related_paths_across_trees(
903
self.interesting_files, lookup_trees)
903
self.interesting_files, lookup_trees)
905
905
interesting_files = None
999
return tree.get_file_sha1(path, file_id)
999
return tree.get_file_sha1(path)
1000
1000
except errors.NoSuchFile:
1002
1002
base_sha1 = get_sha1(self.base_tree, base_path)
1012
1012
(base_ie.executable, lca_executable),
1013
1013
other_ie.executable, this_ie.executable)
1014
1014
if (parent_id_winner == 'this' and name_winner == 'this'
1015
and sha1_winner == 'this' and exec_winner == 'this'):
1015
and sha1_winner == 'this' and exec_winner == 'this'):
1016
1016
# No kind, parent, name, exec, or content change for
1017
1017
# OTHER, so this node is not considered interesting
1022
1022
def get_target(ie, tree, path):
1023
1023
if ie.kind != 'symlink':
1025
return tree.get_symlink_target(path, file_id)
1025
return tree.get_symlink_target(path)
1026
1026
base_target = get_target(base_ie, self.base_tree, base_path)
1027
1027
lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1028
1028
in zip(lca_entries, self._lca_trees, lca_paths)]
1029
this_target = get_target(this_ie, self.this_tree, this_path)
1030
other_target = get_target(other_ie, self.other_tree, other_path)
1029
this_target = get_target(
1030
this_ie, self.this_tree, this_path)
1031
other_target = get_target(
1032
other_ie, self.other_tree, other_path)
1031
1033
target_winner = self._lca_multi_way(
1032
1034
(base_target, lca_targets),
1033
1035
other_target, this_target)
1034
1036
if (parent_id_winner == 'this' and name_winner == 'this'
1035
and target_winner == 'this'):
1037
and target_winner == 'this'):
1036
1038
# No kind, parent, name, or symlink target change
1037
1039
# not interesting
1059
1061
other_ie.name, this_ie.name),
1060
1062
((base_ie.executable, lca_executable),
1061
1063
other_ie.executable, this_ie.executable)
1065
1067
def write_modified(self, results):
1071
1073
file_id = self.working_tree.path2id(wt_relpath)
1072
1074
if file_id is None:
1074
hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
1076
hash = self.working_tree.get_file_sha1(wt_relpath)
1075
1077
if hash is None:
1077
1079
modified_hashes[file_id] = hash
1108
1110
except errors.NoSuchFile:
1110
return tree.is_executable(path, file_id)
1112
return tree.is_executable(path)
1113
1115
def kind(tree, path, file_id=None):
1158
1160
base_val, lca_vals = bases
1159
1161
# Remove 'base_val' from the lca_vals, because it is not interesting
1160
1162
filtered_lca_vals = [lca_val for lca_val in lca_vals
1161
if lca_val != base_val]
1163
if lca_val != base_val]
1162
1164
if len(filtered_lca_vals) == 0:
1163
1165
return Merge3Merger._three_way(base_val, other, this)
1183
1185
# At this point, the lcas disagree, and the tip disagree
1184
1186
return 'conflict'
1186
def merge_names(self, paths):
1187
def get_entry(tree, path):
1189
return next(tree.iter_entries_by_dir(specific_files=[path]))[1]
1190
except StopIteration:
1192
used_base_path, other_path, this_path = paths
1193
this_entry = get_entry(self.this_tree, this_path)
1194
other_entry = get_entry(self.other_tree, other_path)
1195
base_entry = get_entry(self.base_tree, base_path)
1196
entries = (base_entry, other_entry, this_entry)
1199
for entry in entries:
1202
parents.append(None)
1204
names.append(entry.name)
1205
parents.append(entry.parent_id)
1206
return self._merge_names(file_id, paths, parents, names,
1207
resolver=self._three_way)
1209
1188
def _merge_names(self, file_id, paths, parents, names, resolver):
1210
1189
"""Perform a merge on file_id names and parents"""
1211
1190
base_name, other_name, this_name = names
1258
1237
if path is None:
1259
1238
return (None, None)
1261
kind = tree.kind(path, file_id)
1240
kind = tree.kind(path)
1262
1241
except errors.NoSuchFile:
1263
1242
return (None, None)
1264
1243
if kind == "file":
1265
contents = tree.get_file_sha1(path, file_id)
1244
contents = tree.get_file_sha1(path)
1266
1245
elif kind == "symlink":
1267
contents = tree.get_symlink_target(path, file_id)
1246
contents = tree.get_symlink_target(path)
1269
1248
contents = None
1270
1249
return kind, contents
1299
1278
trans_id = self.tt.trans_id_file_id(file_id)
1300
1279
params = MergeFileHookParams(
1301
1280
self, file_id, (base_path, other_path,
1302
this_path), trans_id, this_pair[0],
1281
this_path), trans_id, this_pair[0],
1303
1282
other_pair[0], winner)
1304
1283
hooks = self.active_hooks
1305
1284
hook_status = 'not_applicable'
1320
1299
name = self.tt.final_name(trans_id)
1321
1300
parent_id = self.tt.final_parent(trans_id)
1323
1301
inhibit_content_conflict = False
1324
if params.this_kind is None: # file_id is not in THIS
1302
if params.this_kind is None: # file_id is not in THIS
1325
1303
# Is the name used for a different file_id ?
1326
1304
if self.this_tree.is_versioned(other_path):
1327
1305
# Two entries for the same path
1334
1312
other_path, file_id=file_id,
1335
1313
filter_tree_path=self._get_filter_tree_path(file_id))
1336
1314
inhibit_content_conflict = True
1337
elif params.other_kind is None: # file_id is not in OTHER
1315
elif params.other_kind is None: # file_id is not in OTHER
1338
1316
# Is the name used for a different file_id ?
1339
1317
if self.other_tree.is_versioned(this_path):
1340
1318
# Two entries for the same path again, but here, the other
1351
1329
# This is a contents conflict, because none of the available
1352
1330
# functions could merge it.
1353
1331
file_group = self._dump_conflicts(
1354
name, (base_path, other_path, this_path), parent_id,
1355
file_id, set_version=True)
1332
name, (base_path, other_path, this_path), parent_id,
1333
file_id, set_version=True)
1356
1334
self._raw_conflicts.append(('contents conflict', file_group))
1357
1335
elif hook_status == 'success':
1358
1336
self.tt.create_file(lines, trans_id)
1427
1405
if path is None:
1430
kind = tree.kind(path, file_id)
1408
kind = tree.kind(path)
1431
1409
except errors.NoSuchFile:
1434
1412
if kind != 'file':
1436
return tree.get_file_lines(path, file_id)
1414
return tree.get_file_lines(path)
1438
1416
def text_merge(self, trans_id, paths, file_id):
1439
1417
"""Perform a three-way text merge on a file_id"""
1454
1432
def iter_merge3(retval):
1455
1433
retval["text_conflicts"] = False
1456
for line in m3.merge_lines(name_a = b"TREE",
1457
name_b = b"MERGE-SOURCE",
1458
name_base = b"BASE-REVISION",
1434
for line in m3.merge_lines(name_a=b"TREE",
1435
name_b=b"MERGE-SOURCE",
1436
name_base=b"BASE-REVISION",
1459
1437
start_marker=start_marker,
1460
1438
base_marker=base_marker,
1461
1439
reprocess=self.reprocess):
1521
1499
for suffix, tree, path, lines in data:
1522
1500
if path is not None:
1523
1501
trans_id = self._conflict_file(
1524
name, parent_id, path, tree, file_id, suffix, lines,
1502
name, parent_id, path, tree, file_id, suffix, lines,
1526
1504
file_group.append(trans_id)
1527
1505
if set_version and not versioned:
1528
1506
self.tt.version_file(file_id, trans_id)
1535
1513
name = name + '.' + suffix
1536
1514
trans_id = self.tt.create_path(name, parent_id)
1537
1515
transform.create_from_tree(
1538
self.tt, trans_id, tree, path,
1539
file_id=file_id, chunks=lines,
1540
filter_tree_path=filter_tree_path)
1516
self.tt, trans_id, tree, path,
1517
file_id=file_id, chunks=lines,
1518
filter_tree_path=filter_tree_path)
1541
1519
return trans_id
1543
1521
def merge_executable(self, paths, file_id, file_status):
1544
1522
"""Perform a merge on the execute bit."""
1545
executable = [self.executable(t, p, file_id) for t, p in zip([self.base_tree,
1546
self.other_tree, self.this_tree], paths)]
1523
executable = [self.executable(t, p, file_id)
1524
for t, p in zip([self.base_tree, self.other_tree, self.this_tree], paths)]
1547
1525
self._merge_executable(paths, file_id, executable, file_status,
1548
1526
resolver=self._three_way)
1557
1535
winner = resolver(*executable)
1558
1536
if winner == "conflict":
1559
# There must be a None in here, if we have a conflict, but we
1560
# need executability since file status was not deleted.
1537
# There must be a None in here, if we have a conflict, but we
1538
# need executability since file status was not deleted.
1561
1539
if other_path is None:
1562
1540
winner = "this"
1589
1567
conflict_type = conflict[0]
1590
1568
if conflict_type == 'path conflict':
1591
1569
(trans_id, file_id,
1592
this_parent, this_name,
1593
other_parent, other_name) = conflict[1:]
1570
this_parent, this_name,
1571
other_parent, other_name) = conflict[1:]
1594
1572
if this_parent is None or this_name is None:
1595
1573
this_path = '<deleted>'
1597
parent_path = fp.get_path(
1575
parent_path = fp.get_path(
1598
1576
self.tt.trans_id_file_id(this_parent))
1599
1577
this_path = osutils.pathjoin(parent_path, this_name)
1600
1578
if other_parent is None or other_name is None:
1607
1585
parent_path = ''
1609
parent_path = fp.get_path(
1587
parent_path = fp.get_path(
1610
1588
self.tt.trans_id_file_id(other_parent))
1611
1589
other_path = osutils.pathjoin(parent_path, other_name)
1612
1590
c = _mod_conflicts.Conflict.factory(
1645
1623
# conflict is enough.
1646
1624
for c in cooked_conflicts:
1647
1625
if (c.typestring == 'path conflict'
1648
and c.file_id in content_conflict_file_ids):
1626
and c.file_id in content_conflict_file_ids):
1650
1628
self.cooked_conflicts.append(c)
1651
1629
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1727
1706
def dump_file(self, temp_dir, name, tree, path, file_id=None):
1728
1707
out_path = osutils.pathjoin(temp_dir, name)
1729
1708
with open(out_path, "wb") as out_file:
1730
in_file = tree.get_file(path, file_id=None)
1709
in_file = tree.get_file(path)
1731
1710
for line in in_file:
1732
1711
out_file.write(line)
1733
1712
return out_path
1742
1721
temp_dir = osutils.mkdtemp(prefix="bzr-")
1744
1723
new_file = osutils.pathjoin(temp_dir, "new")
1745
this = self.dump_file(temp_dir, "this", self.this_tree, this_path, file_id)
1746
base = self.dump_file(temp_dir, "base", self.base_tree, base_path, file_id)
1747
other = self.dump_file(temp_dir, "other", self.other_tree, other_path, file_id)
1724
this = self.dump_file(
1725
temp_dir, "this", self.this_tree, this_path, file_id)
1726
base = self.dump_file(
1727
temp_dir, "base", self.base_tree, base_path, file_id)
1728
other = self.dump_file(
1729
temp_dir, "other", self.other_tree, other_path, file_id)
1748
1730
status = breezy.patch.diff3(new_file, this, base, other)
1749
1731
if status not in (0, 1):
1750
1732
raise errors.BzrError("Unhandled diff3 exit code")
1777
1759
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1778
source_subpath, other_rev_id=None):
1760
source_subpath, other_rev_id=None):
1779
1761
"""Create a new MergeIntoMerger object.
1781
1763
source_subpath in other_tree will be effectively copied to
1792
1774
# It is assumed that we are merging a tree that is not in our current
1793
1775
# ancestry, which means we are using the "EmptyTree" as our basis.
1794
1776
null_ancestor_tree = this_tree.branch.repository.revision_tree(
1795
_mod_revision.NULL_REVISION)
1777
_mod_revision.NULL_REVISION)
1796
1778
super(MergeIntoMerger, self).__init__(
1797
1779
this_branch=this_tree.branch,
1798
1780
this_tree=this_tree,
1812
1794
self.reprocess = False
1813
1795
self.interesting_files = None
1814
1796
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1815
target_subdir=self._target_subdir,
1816
source_subpath=self._source_subpath)
1797
target_subdir=self._target_subdir,
1798
source_subpath=self._source_subpath)
1817
1799
if self._source_subpath != '':
1818
1800
# If this isn't a partial merge make sure the revisions will be
1820
1802
self._maybe_fetch(self.other_branch, self.this_branch,
1823
1805
def set_pending(self):
1824
1806
if self._source_subpath != '':
1873
1855
entries = self._entries_to_incorporate()
1874
1856
entries = list(entries)
1875
1857
for num, (entry, parent_id, relpath) in enumerate(entries):
1876
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1858
child_pb.update(gettext('Preparing file merge'),
1877
1860
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1878
1861
path = osutils.pathjoin(self._source_subpath, relpath)
1879
1862
trans_id = transform.new_by_entry(path, self.tt, entry,
1880
parent_trans_id, self.other_tree)
1863
parent_trans_id, self.other_tree)
1881
1864
self._finish_computing_transform()
1883
1866
def _entries_to_incorporate(self):
2085
2068
yield 'killed-a', self.lines_b[b_index]
2086
2069
# handle common lines
2087
for a_index in range(i, i+n):
2070
for a_index in range(i, i + n):
2088
2071
yield 'unchanged', self.lines_a[a_index]
2092
2075
def _get_matching_blocks(self, left_revision, right_revision):
2093
2076
"""Return a description of which sections of two revisions match.
2149
2132
for i, j, n in matcher.get_matching_blocks():
2150
2133
for jj in range(last_j, j):
2151
2134
yield new_plan[jj]
2152
for jj in range(j, j+n):
2135
for jj in range(j, j + n):
2153
2136
plan_line = new_plan[jj]
2154
2137
if plan_line[0] == 'new-b':
2386
2369
are combined, they are written out in the format described in
2387
2370
VersionedFile.plan_merge
2389
if self._head_key is not None: # There was a single head
2372
if self._head_key is not None: # There was a single head
2390
2373
if self._head_key == self.a_key: