17
17
from __future__ import absolute_import
21
19
from .lazy_import import lazy_import
22
20
lazy_import(globals(), """
23
23
from breezy import (
24
24
branch as _mod_branch,
26
26
conflicts as _mod_conflicts,
29
28
graph as _mod_graph,
33
31
revision as _mod_revision,
60
58
def transform_tree(from_tree, to_tree, interesting_files=None):
61
59
with from_tree.lock_tree_write():
62
60
merge_inner(from_tree.branch, to_tree, from_tree,
63
ignore_zero=True, this_tree=from_tree,
64
interesting_files=interesting_files)
61
ignore_zero=True, this_tree=from_tree,
62
interesting_files=interesting_files)
67
65
class MergeHooks(hooks.Hooks):
69
67
def __init__(self):
70
68
hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
71
69
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 "
70
"Called with a breezy.merge.Merger object to create a per file "
71
"merge object when starting a merge. "
72
"Should return either None or a subclass of "
73
"``breezy.merge.AbstractPerFileMerger``. "
74
"Such objects will then be called per file "
75
"that needs to be merged (including when one "
76
"side has deleted the file and the other has changed it). "
77
"See the AbstractPerFileMerger API docs for details on how it is "
82
80
self.add_hook('pre_merge',
83
'Called before a merge. '
84
'Receives a Merger object as the single argument.',
81
'Called before a merge. '
82
'Receives a Merger object as the single argument.',
86
84
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.',
85
'Called after a merge. '
86
'Receives a Merger object as the single argument. '
87
'The return value is ignored.',
93
91
class AbstractPerFileMerger(object):
139
137
# THIS and OTHER aren't both files.
140
138
not params.is_file_merge() or
141
139
# The filename doesn't match
142
not self.file_matches(params)):
140
not self.file_matches(params)):
143
141
return 'not_applicable', None
144
142
return self.merge_matching(params)
253
251
@decorators.cachedproperty
254
252
def base_lines(self):
255
253
"""The lines of the 'base' version of the file."""
256
return self._merger.get_lines(self._merger.base_tree, self.base_path, self.file_id)
254
return self._merger.get_lines(self._merger.base_tree, self.base_path)
258
256
@decorators.cachedproperty
259
257
def this_lines(self):
260
258
"""The lines of the 'this' version of the file."""
261
return self._merger.get_lines(self._merger.this_tree, self.this_path, self.file_id)
259
return self._merger.get_lines(self._merger.this_tree, self.this_path)
263
261
@decorators.cachedproperty
264
262
def other_lines(self):
265
263
"""The lines of the 'other' version of the file."""
266
return self._merger.get_lines(self._merger.other_tree, self.other_path, self.file_id)
264
return self._merger.get_lines(self._merger.other_tree, self.other_path)
269
267
class Merger(object):
372
370
if base_revision_id is not None:
373
371
if (base_revision_id != _mod_revision.NULL_REVISION and
374
372
revision_graph.is_ancestor(
375
base_revision_id, tree.branch.last_revision())):
373
base_revision_id, tree.branch.last_revision())):
376
374
base_revision_id = None
378
376
trace.warning('Performing cherrypick')
379
377
merger = klass.from_revision_ids(tree, other_revision_id,
380
base_revision_id, revision_graph=
378
base_revision_id, revision_graph=revision_graph)
382
379
return merger, verified
490
487
raise errors.NoCommits(self.other_branch)
491
488
if self.other_rev_id is not None:
492
489
self._cached_trees[self.other_rev_id] = self.other_tree
493
self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
490
self._maybe_fetch(self.other_branch,
491
self.this_branch, self.other_basis)
495
493
def set_other_revision(self, revision_id, other_branch):
496
494
"""Set 'other' based on a branch and revision id
533
531
self.base_rev_id = _mod_revision.NULL_REVISION
534
532
elif len(lcas) == 1:
535
533
self.base_rev_id = list(lcas)[0]
536
else: # len(lcas) > 1
534
else: # len(lcas) > 1
537
535
self._is_criss_cross = True
538
536
if len(lcas) > 2:
539
537
# find_unique_lca can only handle 2 nodes, so we have to
541
539
# the graph again, but better than re-implementing
542
540
# find_unique_lca.
543
541
self.base_rev_id = self.revision_graph.find_unique_lca(
544
revisions[0], revisions[1])
542
revisions[0], revisions[1])
546
544
self.base_rev_id = self.revision_graph.find_unique_lca(
548
546
sorted_lca_keys = self.revision_graph.find_merge_order(
549
547
revisions[0], lcas)
550
548
if self.base_rev_id == _mod_revision.NULL_REVISION:
563
561
interesting_revision_ids = set(lcas)
564
562
interesting_revision_ids.add(self.base_rev_id)
565
563
interesting_trees = dict((t.get_revision_id(), t)
566
for t in self.this_branch.repository.revision_trees(
567
interesting_revision_ids))
564
for t in self.this_branch.repository.revision_trees(
565
interesting_revision_ids))
568
566
self._cached_trees.update(interesting_trees)
569
567
if self.base_rev_id in lcas:
570
568
self.base_tree = interesting_trees[self.base_rev_id]
618
616
raise errors.BzrError("Showing base is not supported for this"
619
617
" merge type. %s" % self.merge_type)
620
618
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
621
and not self.base_is_other_ancestor):
619
and not self.base_is_other_ancestor):
622
620
raise errors.CannotReverseCherrypick()
623
621
if self.merge_type.supports_cherrypick:
624
622
kwargs['cherrypick'] = (not self.base_is_ancestor or
641
639
if self.recurse == 'down':
642
640
for relpath, file_id in self.this_tree.iter_references():
643
sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
641
sub_tree = self.this_tree.get_nested_tree(relpath)
644
642
other_revision = self.other_tree.get_reference_revision(
646
if other_revision == sub_tree.last_revision():
644
if other_revision == sub_tree.last_revision():
648
646
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
649
647
sub_merge.merge_type = self.merge_type
653
651
base_tree_path = _mod_tree.find_previous_path(
654
652
self.this_tree, self.base_tree, relpath)
655
653
base_revision = self.base_tree.get_reference_revision(
656
base_tree_path, file_id)
657
655
sub_merge.base_tree = \
658
656
sub_tree.branch.repository.revision_tree(base_revision)
659
657
sub_merge.base_rev_id = base_revision
809
808
# Try merging each entry
810
809
child_pb.update(gettext('Preparing file merge'),
811
810
num, len(entries))
812
self._merge_names(file_id, paths3, parents3, names3, resolver=resolver)
811
self._merge_names(file_id, paths3, parents3,
812
names3, resolver=resolver)
814
814
file_status = self._do_merge_contents(paths3, file_id)
816
816
file_status = 'unmodified'
817
817
self._merge_executable(paths3, file_id, executable3,
818
file_status, resolver=resolver)
818
file_status, resolver=resolver)
819
819
self.tt.fixup_new_roots()
820
820
self._finish_computing_transform()
827
827
with ui.ui_factory.nested_progress_bar() as child_pb:
828
828
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
829
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
829
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
830
830
if self.change_reporter is not None:
831
831
from breezy import delta
832
832
delta.report_changes(
848
848
iterator = self.other_tree.iter_changes(self.base_tree,
849
specific_files=self.interesting_files,
850
extra_trees=[self.this_tree])
849
specific_files=self.interesting_files,
850
extra_trees=[self.this_tree])
851
851
this_interesting_files = self.this_tree.find_related_paths_across_trees(
852
self.interesting_files, trees=[self.other_tree])
852
self.interesting_files, trees=[self.other_tree])
853
853
this_entries = dict(self.this_tree.iter_entries_by_dir(
854
854
specific_files=this_interesting_files))
855
855
for (file_id, paths, changed, versioned, parents, names, kind,
856
856
executable) in iterator:
857
857
if paths[0] is not None:
858
858
this_path = _mod_tree.find_previous_path(
859
self.base_tree, self.this_tree, paths[0])
859
self.base_tree, self.this_tree, paths[0])
861
861
this_path = _mod_tree.find_previous_path(
862
self.other_tree, self.this_tree, paths[1])
862
self.other_tree, self.this_tree, paths[1])
863
863
this_entry = this_entries.get(this_path)
864
864
if this_entry is not None:
865
865
this_name = this_entry.name
873
873
names3 = names + (this_name,)
874
874
paths3 = paths + (this_path, )
875
875
executable3 = executable + (this_executable,)
876
result.append((file_id, changed, paths3, parents3, names3, executable3))
876
result.append((file_id, changed, paths3,
877
parents3, names3, executable3))
879
880
def _entries_lca(self):
900
901
lookup_trees.extend(self._lca_trees)
901
902
# I think we should include the lca trees as well
902
903
interesting_files = self.other_tree.find_related_paths_across_trees(
903
self.interesting_files, lookup_trees)
904
self.interesting_files, lookup_trees)
905
906
interesting_files = None
999
return tree.get_file_sha1(path, file_id)
1000
return tree.get_file_sha1(path)
1000
1001
except errors.NoSuchFile:
1002
1003
base_sha1 = get_sha1(self.base_tree, base_path)
1012
1013
(base_ie.executable, lca_executable),
1013
1014
other_ie.executable, this_ie.executable)
1014
1015
if (parent_id_winner == 'this' and name_winner == 'this'
1015
and sha1_winner == 'this' and exec_winner == 'this'):
1016
and sha1_winner == 'this' and exec_winner == 'this'):
1016
1017
# No kind, parent, name, exec, or content change for
1017
1018
# OTHER, so this node is not considered interesting
1022
1023
def get_target(ie, tree, path):
1023
1024
if ie.kind != 'symlink':
1025
return tree.get_symlink_target(path, file_id)
1026
return tree.get_symlink_target(path)
1026
1027
base_target = get_target(base_ie, self.base_tree, base_path)
1027
1028
lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1028
1029
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)
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)
1031
1034
target_winner = self._lca_multi_way(
1032
1035
(base_target, lca_targets),
1033
1036
other_target, this_target)
1034
1037
if (parent_id_winner == 'this' and name_winner == 'this'
1035
and target_winner == 'this'):
1038
and target_winner == 'this'):
1036
1039
# No kind, parent, name, or symlink target change
1037
1040
# not interesting
1059
1062
other_ie.name, this_ie.name),
1060
1063
((base_ie.executable, lca_executable),
1061
1064
other_ie.executable, this_ie.executable)
1065
1068
def write_modified(self, results):
1071
1074
file_id = self.working_tree.path2id(wt_relpath)
1072
1075
if file_id is None:
1074
hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
1077
hash = self.working_tree.get_file_sha1(wt_relpath)
1075
1078
if hash is None:
1077
1080
modified_hashes[file_id] = hash
1078
1081
self.working_tree.set_merge_modified(modified_hashes)
1081
def parent(entry, file_id):
1082
1085
"""Determine the parent for a file_id (used as a key method)"""
1083
1086
if entry is None:
1085
1088
return entry.parent_id
1088
def name(entry, file_id):
1089
1092
"""Determine the name for a file_id (used as a key method)"""
1090
1093
if entry is None:
1092
1095
return entry.name
1095
def contents_sha1(tree, path, file_id=None):
1098
def contents_sha1(tree, path):
1096
1099
"""Determine the sha1 of the file contents (used as a key method)."""
1098
return tree.get_file_sha1(path, file_id)
1101
return tree.get_file_sha1(path)
1099
1102
except errors.NoSuchFile:
1103
def executable(tree, path, file_id=None):
1106
def executable(tree, path):
1104
1107
"""Determine the executability of a file-id (used as a key method)."""
1106
if tree.kind(path, file_id) != "file":
1109
if tree.kind(path) != "file":
1108
1111
except errors.NoSuchFile:
1110
return tree.is_executable(path, file_id)
1113
return tree.is_executable(path)
1113
def kind(tree, path, file_id=None):
1116
def kind(tree, path):
1114
1117
"""Determine the kind of a file-id (used as a key method)."""
1116
return tree.kind(path, file_id)
1119
return tree.kind(path)
1117
1120
except errors.NoSuchFile:
1158
1161
base_val, lca_vals = bases
1159
1162
# Remove 'base_val' from the lca_vals, because it is not interesting
1160
1163
filtered_lca_vals = [lca_val for lca_val in lca_vals
1161
if lca_val != base_val]
1164
if lca_val != base_val]
1162
1165
if len(filtered_lca_vals) == 0:
1163
1166
return Merge3Merger._three_way(base_val, other, this)
1183
1186
# At this point, the lcas disagree, and the tip disagree
1184
1187
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
1189
def _merge_names(self, file_id, paths, parents, names, resolver):
1210
1190
"""Perform a merge on file_id names and parents"""
1211
1191
base_name, other_name, this_name = names
1258
1238
if path is None:
1259
1239
return (None, None)
1261
kind = tree.kind(path, file_id)
1241
kind = tree.kind(path)
1262
1242
except errors.NoSuchFile:
1263
1243
return (None, None)
1264
1244
if kind == "file":
1265
contents = tree.get_file_sha1(path, file_id)
1245
contents = tree.get_file_sha1(path)
1266
1246
elif kind == "symlink":
1267
contents = tree.get_symlink_target(path, file_id)
1247
contents = tree.get_symlink_target(path)
1269
1249
contents = None
1270
1250
return kind, contents
1299
1279
trans_id = self.tt.trans_id_file_id(file_id)
1300
1280
params = MergeFileHookParams(
1301
1281
self, file_id, (base_path, other_path,
1302
this_path), trans_id, this_pair[0],
1282
this_path), trans_id, this_pair[0],
1303
1283
other_pair[0], winner)
1304
1284
hooks = self.active_hooks
1305
1285
hook_status = 'not_applicable'
1320
1300
name = self.tt.final_name(trans_id)
1321
1301
parent_id = self.tt.final_parent(trans_id)
1323
1302
inhibit_content_conflict = False
1324
if params.this_kind is None: # file_id is not in THIS
1303
if params.this_kind is None: # file_id is not in THIS
1325
1304
# Is the name used for a different file_id ?
1326
1305
if self.this_tree.is_versioned(other_path):
1327
1306
# Two entries for the same path
1334
1313
other_path, file_id=file_id,
1335
1314
filter_tree_path=self._get_filter_tree_path(file_id))
1336
1315
inhibit_content_conflict = True
1337
elif params.other_kind is None: # file_id is not in OTHER
1316
elif params.other_kind is None: # file_id is not in OTHER
1338
1317
# Is the name used for a different file_id ?
1339
1318
if self.other_tree.is_versioned(this_path):
1340
1319
# Two entries for the same path again, but here, the other
1351
1330
# This is a contents conflict, because none of the available
1352
1331
# functions could merge it.
1353
1332
file_group = self._dump_conflicts(
1354
name, (base_path, other_path, this_path), parent_id,
1355
file_id, set_version=True)
1333
name, (base_path, other_path, this_path), parent_id,
1334
file_id, set_version=True)
1356
1335
self._raw_conflicts.append(('contents conflict', file_group))
1357
1336
elif hook_status == 'success':
1358
1337
self.tt.create_file(lines, trans_id)
1423
1402
return 'not_applicable', None
1425
def get_lines(self, tree, path, file_id=None):
1404
def get_lines(self, tree, path):
1426
1405
"""Return the lines in a file, or an empty list."""
1427
1406
if path is None:
1430
kind = tree.kind(path, file_id)
1409
kind = tree.kind(path)
1431
1410
except errors.NoSuchFile:
1434
1413
if kind != 'file':
1436
return tree.get_file_lines(path, file_id)
1415
return tree.get_file_lines(path)
1438
1417
def text_merge(self, trans_id, paths, file_id):
1439
1418
"""Perform a three-way text merge on a file_id"""
1440
1419
# it's possible that we got here with base as a different type.
1441
1420
# if so, we just want two-way text conflicts.
1442
1421
base_path, other_path, this_path = paths
1443
base_lines = self.get_lines(self.base_tree, base_path, file_id)
1444
other_lines = self.get_lines(self.other_tree, other_path, file_id)
1445
this_lines = self.get_lines(self.this_tree, this_path, file_id)
1422
base_lines = self.get_lines(self.base_tree, base_path)
1423
other_lines = self.get_lines(self.other_tree, other_path)
1424
this_lines = self.get_lines(self.this_tree, this_path)
1446
1425
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1447
1426
is_cherrypick=self.cherrypick)
1448
1427
start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1454
1433
def iter_merge3(retval):
1455
1434
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",
1435
for line in m3.merge_lines(name_a=b"TREE",
1436
name_b=b"MERGE-SOURCE",
1437
name_base=b"BASE-REVISION",
1459
1438
start_marker=start_marker,
1460
1439
base_marker=base_marker,
1461
1440
reprocess=self.reprocess):
1521
1500
for suffix, tree, path, lines in data:
1522
1501
if path is not None:
1523
1502
trans_id = self._conflict_file(
1524
name, parent_id, path, tree, file_id, suffix, lines,
1503
name, parent_id, path, tree, file_id, suffix, lines,
1526
1505
file_group.append(trans_id)
1527
1506
if set_version and not versioned:
1528
1507
self.tt.version_file(file_id, trans_id)
1535
1514
name = name + '.' + suffix
1536
1515
trans_id = self.tt.create_path(name, parent_id)
1537
1516
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)
1517
self.tt, trans_id, tree, path,
1518
file_id=file_id, chunks=lines,
1519
filter_tree_path=filter_tree_path)
1541
1520
return trans_id
1543
1522
def merge_executable(self, paths, file_id, file_status):
1544
1523
"""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)]
1524
executable = [self.executable(t, p, file_id)
1525
for t, p in zip([self.base_tree, self.other_tree, self.this_tree], paths)]
1547
1526
self._merge_executable(paths, file_id, executable, file_status,
1548
1527
resolver=self._three_way)
1557
1536
winner = resolver(*executable)
1558
1537
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.
1538
# There must be a None in here, if we have a conflict, but we
1539
# need executability since file status was not deleted.
1561
1540
if other_path is None:
1562
1541
winner = "this"
1589
1568
conflict_type = conflict[0]
1590
1569
if conflict_type == 'path conflict':
1591
1570
(trans_id, file_id,
1592
this_parent, this_name,
1593
other_parent, other_name) = conflict[1:]
1571
this_parent, this_name,
1572
other_parent, other_name) = conflict[1:]
1594
1573
if this_parent is None or this_name is None:
1595
1574
this_path = '<deleted>'
1597
parent_path = fp.get_path(
1576
parent_path = fp.get_path(
1598
1577
self.tt.trans_id_file_id(this_parent))
1599
1578
this_path = osutils.pathjoin(parent_path, this_name)
1600
1579
if other_parent is None or other_name is None:
1607
1586
parent_path = ''
1609
parent_path = fp.get_path(
1588
parent_path = fp.get_path(
1610
1589
self.tt.trans_id_file_id(other_parent))
1611
1590
other_path = osutils.pathjoin(parent_path, other_name)
1612
1591
c = _mod_conflicts.Conflict.factory(
1645
1624
# conflict is enough.
1646
1625
for c in cooked_conflicts:
1647
1626
if (c.typestring == 'path conflict'
1648
and c.file_id in content_conflict_file_ids):
1627
and c.file_id in content_conflict_file_ids):
1650
1629
self.cooked_conflicts.append(c)
1651
1630
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1719
1698
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1722
1702
class Diff3Merger(Merge3Merger):
1723
1703
"""Three-way merger using external diff3 for text merging"""
1725
1705
requires_file_merge_plan = False
1727
def dump_file(self, temp_dir, name, tree, path, file_id=None):
1707
def dump_file(self, temp_dir, name, tree, path):
1728
1708
out_path = osutils.pathjoin(temp_dir, name)
1729
1709
with open(out_path, "wb") as out_file:
1730
in_file = tree.get_file(path, file_id=None)
1710
in_file = tree.get_file(path)
1731
1711
for line in in_file:
1732
1712
out_file.write(line)
1733
1713
return out_path
1742
1722
temp_dir = osutils.mkdtemp(prefix="bzr-")
1744
1724
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)
1725
this = self.dump_file(
1726
temp_dir, "this", self.this_tree, this_path)
1727
base = self.dump_file(
1728
temp_dir, "base", self.base_tree, base_path)
1729
other = self.dump_file(
1730
temp_dir, "other", self.other_tree, other_path)
1748
1731
status = breezy.patch.diff3(new_file, this, base, other)
1749
1732
if status not in (0, 1):
1750
1733
raise errors.BzrError("Unhandled diff3 exit code")
1777
1760
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1778
source_subpath, other_rev_id=None):
1761
source_subpath, other_rev_id=None):
1779
1762
"""Create a new MergeIntoMerger object.
1781
1764
source_subpath in other_tree will be effectively copied to
1792
1775
# It is assumed that we are merging a tree that is not in our current
1793
1776
# ancestry, which means we are using the "EmptyTree" as our basis.
1794
1777
null_ancestor_tree = this_tree.branch.repository.revision_tree(
1795
_mod_revision.NULL_REVISION)
1778
_mod_revision.NULL_REVISION)
1796
1779
super(MergeIntoMerger, self).__init__(
1797
1780
this_branch=this_tree.branch,
1798
1781
this_tree=this_tree,
1812
1795
self.reprocess = False
1813
1796
self.interesting_files = None
1814
1797
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1815
target_subdir=self._target_subdir,
1816
source_subpath=self._source_subpath)
1798
target_subdir=self._target_subdir,
1799
source_subpath=self._source_subpath)
1817
1800
if self._source_subpath != '':
1818
1801
# If this isn't a partial merge make sure the revisions will be
1820
1803
self._maybe_fetch(self.other_branch, self.this_branch,
1823
1806
def set_pending(self):
1824
1807
if self._source_subpath != '':
1873
1856
entries = self._entries_to_incorporate()
1874
1857
entries = list(entries)
1875
1858
for num, (entry, parent_id, relpath) in enumerate(entries):
1876
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1859
child_pb.update(gettext('Preparing file merge'),
1877
1861
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1878
1862
path = osutils.pathjoin(self._source_subpath, relpath)
1879
1863
trans_id = transform.new_by_entry(path, self.tt, entry,
1880
parent_trans_id, self.other_tree)
1864
parent_trans_id, self.other_tree)
1881
1865
self._finish_computing_transform()
1883
1867
def _entries_to_incorporate(self):
2085
2069
yield 'killed-a', self.lines_b[b_index]
2086
2070
# handle common lines
2087
for a_index in range(i, i+n):
2071
for a_index in range(i, i + n):
2088
2072
yield 'unchanged', self.lines_a[a_index]
2092
2076
def _get_matching_blocks(self, left_revision, right_revision):
2093
2077
"""Return a description of which sections of two revisions match.
2149
2133
for i, j, n in matcher.get_matching_blocks():
2150
2134
for jj in range(last_j, j):
2151
2135
yield new_plan[jj]
2152
for jj in range(j, j+n):
2136
for jj in range(j, j + n):
2153
2137
plan_line = new_plan[jj]
2154
2138
if plan_line[0] == 'new-b':
2386
2370
are combined, they are written out in the format described in
2387
2371
VersionedFile.plan_merge
2389
if self._head_key is not None: # There was a single head
2373
if self._head_key is not None: # There was a single head
2390
2374
if self._head_key == self.a_key: