15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from itertools import chain
23
18
from bzrlib import (
19
branch as _mod_branch,
20
conflicts as _mod_conflicts,
26
23
graph as _mod_graph,
30
29
revision as _mod_revision,
34
from bzrlib.branch import Branch
35
from bzrlib.conflicts import ConflictList, Conflict
36
from bzrlib.errors import (BzrCommandError,
46
WorkingTreeNotRevision,
49
from bzrlib.graph import Graph
50
from bzrlib.merge3 import Merge3
51
from bzrlib.osutils import rename, pathjoin
52
from progress import DummyProgress, ProgressPhase
53
from bzrlib.revision import (NULL_REVISION, ensure_null)
54
from bzrlib.textfile import check_text_lines
55
from bzrlib.trace import mutter, warning, note, is_quiet
56
from bzrlib.transform import (TransformPreview, TreeTransform,
57
resolve_conflicts, cook_conflicts,
58
conflict_pass, FinalPaths, create_from_tree,
59
unique_add, ROOT_PARENT)
60
from bzrlib.versionedfile import PlanWeaveMerge
63
38
# TODO: Report back as changes are merged in
184
159
base_revision_id, tree.branch.last_revision())):
185
160
base_revision_id = None
187
warning('Performing cherrypick')
162
trace.warning('Performing cherrypick')
188
163
merger = klass.from_revision_ids(pb, tree, other_revision_id,
189
164
base_revision_id, revision_graph=
242
217
if revno is None:
243
218
tree = workingtree.WorkingTree.open_containing(location)[0]
244
219
return tree.branch, tree
245
branch = Branch.open_containing(location, possible_transports)[0]
220
branch = _mod_branch.Branch.open_containing(
221
location, possible_transports)[0]
247
223
revision_id = branch.last_revision()
249
225
revision_id = branch.get_rev_id(revno)
250
revision_id = ensure_null(revision_id)
226
revision_id = _mod_revision.ensure_null(revision_id)
251
227
return branch, self.revision_tree(revision_id, branch)
253
229
def ensure_revision_trees(self):
271
247
if self.this_rev_id is None:
272
248
if self.this_basis_tree.get_file_sha1(file_id) != \
273
249
self.this_tree.get_file_sha1(file_id):
274
raise WorkingTreeNotRevision(self.this_tree)
250
raise errors.WorkingTreeNotRevision(self.this_tree)
276
252
trees = (self.this_basis_tree, self.other_tree)
277
253
return [get_id(tree, file_id) for tree in trees]
279
255
def check_basis(self, check_clean, require_commits=True):
280
256
if self.this_basis is None and require_commits is True:
281
raise BzrCommandError("This branch has no commits."
282
" (perhaps you would prefer 'bzr pull')")
257
raise errors.BzrCommandError(
258
"This branch has no commits."
259
" (perhaps you would prefer 'bzr pull')")
284
261
self.compare_basis()
285
262
if self.this_basis != self.this_rev_id:
333
310
self.other_rev_id = _mod_revision.ensure_null(
334
311
self.other_branch.last_revision())
335
312
if _mod_revision.is_null(self.other_rev_id):
336
raise NoCommits(self.other_branch)
313
raise errors.NoCommits(self.other_branch)
337
314
self.other_basis = self.other_rev_id
338
315
elif other_revision[1] is not None:
339
316
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
342
319
self.other_rev_id = None
343
320
self.other_basis = self.other_branch.last_revision()
344
321
if self.other_basis is None:
345
raise NoCommits(self.other_branch)
322
raise errors.NoCommits(self.other_branch)
346
323
if self.other_rev_id is not None:
347
324
self._cached_trees[self.other_rev_id] = self.other_tree
348
325
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
375
352
target.fetch(source, revision_id)
377
354
def find_base(self):
378
revisions = [ensure_null(self.this_basis),
379
ensure_null(self.other_basis)]
380
if NULL_REVISION in revisions:
381
self.base_rev_id = NULL_REVISION
355
revisions = [_mod_revision.ensure_null(self.this_basis),
356
_mod_revision.ensure_null(self.other_basis)]
357
if _mod_revision.NULL_REVISION in revisions:
358
self.base_rev_id = _mod_revision.NULL_REVISION
382
359
self.base_tree = self.revision_tree(self.base_rev_id)
383
360
self._is_criss_cross = False
385
362
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
386
363
self._is_criss_cross = False
387
364
if len(lcas) == 0:
388
self.base_rev_id = NULL_REVISION
365
self.base_rev_id = _mod_revision.NULL_REVISION
389
366
elif len(lcas) == 1:
390
367
self.base_rev_id = list(lcas)[0]
391
368
else: # len(lcas) > 1
400
377
self.base_rev_id = self.revision_graph.find_unique_lca(
402
379
self._is_criss_cross = True
403
if self.base_rev_id == NULL_REVISION:
404
raise UnrelatedBranches()
380
if self.base_rev_id == _mod_revision.NULL_REVISION:
381
raise errors.UnrelatedBranches()
405
382
if self._is_criss_cross:
406
warning('Warning: criss-cross merge encountered. See bzr'
407
' help criss-cross.')
408
mutter('Criss-cross lcas: %r' % lcas)
383
trace.warning('Warning: criss-cross merge encountered. See bzr'
384
' help criss-cross.')
385
trace.mutter('Criss-cross lcas: %r' % lcas)
409
386
interesting_revision_ids = [self.base_rev_id]
410
387
interesting_revision_ids.extend(lcas)
411
388
interesting_trees = dict((t.get_revision_id(), t)
421
398
self.base_tree = self.revision_tree(self.base_rev_id)
422
399
self.base_is_ancestor = True
423
400
self.base_is_other_ancestor = True
424
mutter('Base revid: %r' % self.base_rev_id)
401
trace.mutter('Base revid: %r' % self.base_rev_id)
426
403
def set_base(self, base_revision):
427
404
"""Set the base revision to use for the merge.
429
406
:param base_revision: A 2-list containing a path and revision number.
431
mutter("doing merge() with no base_revision specified")
408
trace.mutter("doing merge() with no base_revision specified")
432
409
if base_revision == [None, None]:
454
431
if self.merge_type.supports_reprocess:
455
432
kwargs['reprocess'] = self.reprocess
456
433
elif self.reprocess:
457
raise BzrError("Conflict reduction is not supported for merge"
458
" type %s." % self.merge_type)
434
raise errors.BzrError(
435
"Conflict reduction is not supported for merge"
436
" type %s." % self.merge_type)
459
437
if self.merge_type.supports_show_base:
460
438
kwargs['show_base'] = self.show_base
461
439
elif self.show_base:
462
raise BzrError("Showing base is not supported for this"
463
" merge type. %s" % self.merge_type)
440
raise errors.BzrError("Showing base is not supported for this"
441
" merge type. %s" % self.merge_type)
464
442
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
465
443
and not self.base_is_other_ancestor):
466
444
raise errors.CannotReverseCherrypick()
516
494
self.this_tree.unlock()
517
495
if len(merge.cooked_conflicts) == 0:
518
if not self.ignore_zero and not is_quiet():
519
note("All changes applied successfully.")
496
if not self.ignore_zero and not trace.is_quiet():
497
trace.note("All changes applied successfully.")
521
note("%d conflicts encountered." % len(merge.cooked_conflicts))
499
trace.note("%d conflicts encountered."
500
% len(merge.cooked_conflicts))
523
502
return len(merge.cooked_conflicts)
554
533
def __init__(self, working_tree, this_tree, base_tree, other_tree,
555
534
interesting_ids=None, reprocess=False, show_base=False,
556
pb=DummyProgress(), pp=None, change_reporter=None,
535
pb=progress.DummyProgress(), pp=None, change_reporter=None,
557
536
interesting_files=None, do_merge=True,
558
537
cherrypick=False, lca_trees=None):
559
538
"""Initialize the merger object and perform the merge.
605
584
self.change_reporter = change_reporter
606
585
self.cherrypick = cherrypick
607
586
if self.pp is None:
608
self.pp = ProgressPhase("Merge phase", 3, self.pb)
587
self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
614
593
self.base_tree.lock_read()
615
594
self.other_tree.lock_read()
617
self.tt = TreeTransform(self.this_tree, self.pb)
596
self.tt = transform.TreeTransform(self.this_tree, self.pb)
619
598
self.pp.next_phase()
620
599
self._compute_transform()
636
615
def make_preview_transform(self):
637
616
self.base_tree.lock_read()
638
617
self.other_tree.lock_read()
639
self.tt = TransformPreview(self.this_tree)
618
self.tt = transform.TransformPreview(self.this_tree)
641
620
self.pp.next_phase()
642
621
self._compute_transform()
672
651
self.pp.next_phase()
673
652
child_pb = ui.ui_factory.nested_progress_bar()
675
fs_conflicts = resolve_conflicts(self.tt, child_pb,
676
lambda t, c: conflict_pass(t, c, self.other_tree))
654
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
655
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
678
657
child_pb.finished()
679
658
if self.change_reporter is not None:
682
661
self.tt.iter_changes(), self.change_reporter)
683
662
self.cook_conflicts(fs_conflicts)
684
663
for conflict in self.cooked_conflicts:
664
trace.warning(conflict)
687
666
def _entries3(self):
688
667
"""Gather data about files modified between three trees.
890
869
def fix_root(self):
892
871
self.tt.final_kind(self.tt.root)
872
except errors.NoSuchFile:
894
873
self.tt.cancel_deletion(self.tt.root)
895
874
if self.tt.final_file_id(self.tt.root) is None:
896
875
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1162
1141
self.tt.delete_contents(trans_id)
1163
1142
if file_id in self.other_tree:
1164
1143
# OTHER changed the file
1165
create_from_tree(self.tt, trans_id,
1166
self.other_tree, file_id)
1144
transform.create_from_tree(self.tt, trans_id,
1145
self.other_tree, file_id)
1167
1146
if not file_in_this:
1168
1147
self.tt.version_file(file_id, trans_id)
1169
1148
return "modified"
1180
1159
# have agreement that output should be a file.
1182
1161
self.text_merge(file_id, trans_id)
1162
except errors.BinaryFile:
1184
1163
return contents_conflict()
1185
1164
if file_id not in self.this_tree:
1186
1165
self.tt.version_file(file_id, trans_id)
1188
1167
self.tt.tree_kind(trans_id)
1189
1168
self.tt.delete_contents(trans_id)
1169
except errors.NoSuchFile:
1192
1171
return "modified"
1211
1190
base_lines = []
1212
1191
other_lines = self.get_lines(self.other_tree, file_id)
1213
1192
this_lines = self.get_lines(self.this_tree, file_id)
1214
m3 = Merge3(base_lines, this_lines, other_lines,
1215
is_cherrypick=self.cherrypick)
1193
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1194
is_cherrypick=self.cherrypick)
1216
1195
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1217
1196
if self.show_base is True:
1218
1197
base_marker = '|' * 7
1273
1252
"""Emit a single conflict file."""
1274
1253
name = name + '.' + suffix
1275
1254
trans_id = self.tt.create_path(name, parent_id)
1276
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1255
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines)
1277
1256
return trans_id
1279
1258
def merge_executable(self, file_id, file_status):
1321
1300
def cook_conflicts(self, fs_conflicts):
1322
1301
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1323
from conflicts import Conflict
1324
1302
name_conflicts = {}
1325
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1326
fp = FinalPaths(self.tt)
1303
self.cooked_conflicts.extend(transform.cook_conflicts(
1304
fs_conflicts, self.tt))
1305
fp = transform.FinalPaths(self.tt)
1327
1306
for conflict in self._raw_conflicts:
1328
1307
conflict_type = conflict[0]
1329
1308
if conflict_type in ('name conflict', 'parent conflict'):
1331
1310
conflict_args = conflict[2:]
1332
1311
if trans_id not in name_conflicts:
1333
1312
name_conflicts[trans_id] = {}
1334
unique_add(name_conflicts[trans_id], conflict_type,
1313
transform.unique_add(name_conflicts[trans_id], conflict_type,
1336
1315
if conflict_type == 'contents conflict':
1337
1316
for trans_id in conflict[1]:
1338
1317
file_id = self.tt.final_file_id(trans_id)
1343
1322
if path.endswith(suffix):
1344
1323
path = path[:-len(suffix)]
1346
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1325
c = _mod_conflicts.Conflict.factory(conflict_type,
1326
path=path, file_id=file_id)
1347
1327
self.cooked_conflicts.append(c)
1348
1328
if conflict_type == 'text conflict':
1349
1329
trans_id = conflict[1]
1350
1330
path = fp.get_path(trans_id)
1351
1331
file_id = self.tt.final_file_id(trans_id)
1352
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1332
c = _mod_conflicts.Conflict.factory(conflict_type,
1333
path=path, file_id=file_id)
1353
1334
self.cooked_conflicts.append(c)
1355
1336
for trans_id, conflicts in name_conflicts.iteritems():
1370
1351
if this_parent is not None and this_name is not None:
1371
1352
this_parent_path = \
1372
1353
fp.get_path(self.tt.trans_id_file_id(this_parent))
1373
this_path = pathjoin(this_parent_path, this_name)
1354
this_path = osutils.pathjoin(this_parent_path, this_name)
1375
1356
this_path = "<deleted>"
1376
1357
file_id = self.tt.final_file_id(trans_id)
1377
c = Conflict.factory('path conflict', path=this_path,
1378
conflict_path=other_path, file_id=file_id)
1358
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1359
conflict_path=other_path,
1379
1361
self.cooked_conflicts.append(c)
1380
self.cooked_conflicts.sort(key=Conflict.sort_key)
1362
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1383
1365
class WeaveMerger(Merge3Merger):
1404
1386
name = self.tt.final_name(trans_id) + '.plan'
1405
1387
contents = ('%10s|%s' % l for l in plan)
1406
1388
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1407
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1408
'>>>>>>> MERGE-SOURCE\n')
1389
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1390
'>>>>>>> MERGE-SOURCE\n')
1409
1391
return textmerge.merge_lines(self.reprocess)
1411
1393
def text_merge(self, file_id, trans_id):
1417
1399
lines = list(lines)
1418
1400
# Note we're checking whether the OUTPUT is binary in this case,
1419
1401
# because we don't want to get into weave merge guts.
1420
check_text_lines(lines)
1402
textfile.check_text_lines(lines)
1421
1403
self.tt.create_file(lines, trans_id)
1423
1405
self._raw_conflicts.append(('text conflict', trans_id))
1447
1429
name = self.tt.final_name(trans_id) + '.plan'
1448
1430
contents = ('%10s|%s' % l for l in plan)
1449
1431
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1450
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1451
'>>>>>>> MERGE-SOURCE\n')
1432
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1433
'>>>>>>> MERGE-SOURCE\n')
1452
1434
return textmerge.merge_lines(self.reprocess)
1456
1438
"""Three-way merger using external diff3 for text merging"""
1458
1440
def dump_file(self, temp_dir, name, tree, file_id):
1459
out_path = pathjoin(temp_dir, name)
1441
out_path = osutils.pathjoin(temp_dir, name)
1460
1442
out_file = open(out_path, "wb")
1462
1444
in_file = tree.get_file(file_id)
1474
1456
import bzrlib.patch
1475
1457
temp_dir = osutils.mkdtemp(prefix="bzr-")
1477
new_file = pathjoin(temp_dir, "new")
1459
new_file = osutils.pathjoin(temp_dir, "new")
1478
1460
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1479
1461
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1480
1462
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1481
1463
status = bzrlib.patch.diff3(new_file, this, base, other)
1482
1464
if status not in (0, 1):
1483
raise BzrError("Unhandled diff3 exit code")
1465
raise errors.BzrError("Unhandled diff3 exit code")
1484
1466
f = open(new_file, 'rb')
1486
1468
self.tt.create_file(f, trans_id)
1513
1495
branch.get_revision_tree(base_revision))'
1515
1497
if this_tree is None:
1516
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1517
"parameter as of bzrlib version 0.8.")
1498
raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1499
"parameter as of bzrlib version 0.8.")
1518
1500
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1519
1501
pb=pb, change_reporter=change_reporter)
1520
1502
merger.backup_files = backup_files
1738
1720
super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1739
1721
self.a_key = self._key_prefix + (self.a_rev,)
1740
1722
self.b_key = self._key_prefix + (self.b_rev,)
1741
self.graph = Graph(self.vf)
1723
self.graph = _mod_graph.Graph(self.vf)
1742
1724
heads = self.graph.heads((self.a_key, self.b_key))
1743
1725
if len(heads) == 1:
1744
1726
# one side dominates, so we can just return its values, yay for
1752
mutter('found dominating revision for %s\n%s > %s', self.vf,
1753
self._head_key[-1], other)
1734
trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
1735
self._head_key[-1], other)
1754
1736
self._weave = None
1756
1738
self._head_key = None
1771
1753
next_lcas = self.graph.find_lca(*cur_ancestors)
1772
1754
# Map a plain NULL_REVISION to a simple no-ancestors
1773
if next_lcas == set([NULL_REVISION]):
1755
if next_lcas == set([_mod_revision.NULL_REVISION]):
1775
1757
# Order the lca's based on when they were merged into the tip
1776
1758
# While the actual merge portion of weave merge uses a set() of
1788
1770
elif len(next_lcas) > 2:
1789
1771
# More than 2 lca's, fall back to grabbing all nodes between
1790
1772
# this and the unique lca.
1791
mutter('More than 2 LCAs, falling back to all nodes for:'
1792
' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
1773
trace.mutter('More than 2 LCAs, falling back to all nodes for:'
1775
self.a_key, self.b_key, cur_ancestors)
1793
1776
cur_lcas = next_lcas
1794
1777
while len(cur_lcas) > 1:
1795
1778
cur_lcas = self.graph.find_lca(*cur_lcas)
1798
1781
unique_lca = None
1800
1783
unique_lca = list(cur_lcas)[0]
1801
if unique_lca == NULL_REVISION:
1784
if unique_lca == _mod_revision.NULL_REVISION:
1802
1785
# find_lca will return a plain 'NULL_REVISION' rather
1803
1786
# than a key tuple when there is no common ancestor, we
1804
1787
# prefer to just use None, because it doesn't confuse
1827
1810
# We remove NULL_REVISION because it isn't a proper tuple key, and
1828
1811
# thus confuses things like _get_interesting_texts, and our logic
1829
1812
# to add the texts into the memory weave.
1830
if NULL_REVISION in parent_map:
1831
parent_map.pop(NULL_REVISION)
1813
if _mod_revision.NULL_REVISION in parent_map:
1814
parent_map.pop(_mod_revision.NULL_REVISION)
1833
1816
interesting = set()
1834
1817
for tip in tip_keys: