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
38
from bzrlib.symbol_versioning import (
63
42
# TODO: Report back as changes are merged in
66
45
def transform_tree(from_tree, to_tree, interesting_ids=None):
67
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
interesting_ids=interesting_ids, this_tree=from_tree)
46
from_tree.lock_tree_write()
48
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
49
interesting_ids=interesting_ids, this_tree=from_tree)
71
54
class Merger(object):
102
85
self._is_criss_cross = None
103
86
self._lca_trees = None
88
def cache_trees_with_revision_ids(self, trees):
89
"""Cache any tree in trees if it has a revision_id."""
90
for maybe_tree in trees:
91
if maybe_tree is None:
94
rev_id = maybe_tree.get_revision_id()
95
except AttributeError:
97
self._cached_trees[rev_id] = maybe_tree
106
100
def revision_graph(self):
107
101
if self._revision_graph is None:
169
163
base_revision_id, tree.branch.last_revision())):
170
164
base_revision_id = None
172
warning('Performing cherrypick')
166
trace.warning('Performing cherrypick')
173
167
merger = klass.from_revision_ids(pb, tree, other_revision_id,
174
168
base_revision_id, revision_graph=
227
221
if revno is None:
228
222
tree = workingtree.WorkingTree.open_containing(location)[0]
229
223
return tree.branch, tree
230
branch = Branch.open_containing(location, possible_transports)[0]
224
branch = _mod_branch.Branch.open_containing(
225
location, possible_transports)[0]
232
227
revision_id = branch.last_revision()
234
229
revision_id = branch.get_rev_id(revno)
235
revision_id = ensure_null(revision_id)
230
revision_id = _mod_revision.ensure_null(revision_id)
236
231
return branch, self.revision_tree(revision_id, branch)
233
@deprecated_method(deprecated_in((2, 1, 0)))
238
234
def ensure_revision_trees(self):
239
235
if self.this_revision_tree is None:
240
236
self.this_basis_tree = self.revision_tree(self.this_basis)
248
244
other_rev_id = self.other_basis
249
245
self.other_tree = other_basis_tree
247
@deprecated_method(deprecated_in((2, 1, 0)))
251
248
def file_revisions(self, file_id):
252
249
self.ensure_revision_trees()
253
250
def get_id(tree, file_id):
256
253
if self.this_rev_id is None:
257
254
if self.this_basis_tree.get_file_sha1(file_id) != \
258
255
self.this_tree.get_file_sha1(file_id):
259
raise WorkingTreeNotRevision(self.this_tree)
256
raise errors.WorkingTreeNotRevision(self.this_tree)
261
258
trees = (self.this_basis_tree, self.other_tree)
262
259
return [get_id(tree, file_id) for tree in trees]
261
@deprecated_method(deprecated_in((2, 1, 0)))
264
262
def check_basis(self, check_clean, require_commits=True):
265
263
if self.this_basis is None and require_commits is True:
266
raise BzrCommandError("This branch has no commits."
267
" (perhaps you would prefer 'bzr pull')")
264
raise errors.BzrCommandError(
265
"This branch has no commits."
266
" (perhaps you would prefer 'bzr pull')")
269
268
self.compare_basis()
270
269
if self.this_basis != self.this_rev_id:
271
270
raise errors.UncommittedChanges(self.this_tree)
272
@deprecated_method(deprecated_in((2, 1, 0)))
273
273
def compare_basis(self):
275
275
basis_tree = self.revision_tree(self.this_tree.last_revision())
318
319
self.other_rev_id = _mod_revision.ensure_null(
319
320
self.other_branch.last_revision())
320
321
if _mod_revision.is_null(self.other_rev_id):
321
raise NoCommits(self.other_branch)
322
raise errors.NoCommits(self.other_branch)
322
323
self.other_basis = self.other_rev_id
323
324
elif other_revision[1] is not None:
324
325
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
327
328
self.other_rev_id = None
328
329
self.other_basis = self.other_branch.last_revision()
329
330
if self.other_basis is None:
330
raise NoCommits(self.other_branch)
331
raise errors.NoCommits(self.other_branch)
331
332
if self.other_rev_id is not None:
332
333
self._cached_trees[self.other_rev_id] = self.other_tree
333
334
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
360
361
target.fetch(source, revision_id)
362
363
def find_base(self):
363
revisions = [ensure_null(self.this_basis),
364
ensure_null(self.other_basis)]
365
if NULL_REVISION in revisions:
366
self.base_rev_id = NULL_REVISION
364
revisions = [_mod_revision.ensure_null(self.this_basis),
365
_mod_revision.ensure_null(self.other_basis)]
366
if _mod_revision.NULL_REVISION in revisions:
367
self.base_rev_id = _mod_revision.NULL_REVISION
367
368
self.base_tree = self.revision_tree(self.base_rev_id)
368
369
self._is_criss_cross = False
370
371
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
371
372
self._is_criss_cross = False
372
373
if len(lcas) == 0:
373
self.base_rev_id = NULL_REVISION
374
self.base_rev_id = _mod_revision.NULL_REVISION
374
375
elif len(lcas) == 1:
375
376
self.base_rev_id = list(lcas)[0]
376
377
else: # len(lcas) > 1
385
386
self.base_rev_id = self.revision_graph.find_unique_lca(
387
388
self._is_criss_cross = True
388
if self.base_rev_id == NULL_REVISION:
389
raise UnrelatedBranches()
389
if self.base_rev_id == _mod_revision.NULL_REVISION:
390
raise errors.UnrelatedBranches()
390
391
if self._is_criss_cross:
391
warning('Warning: criss-cross merge encountered. See bzr'
392
' help criss-cross.')
393
mutter('Criss-cross lcas: %r' % lcas)
392
trace.warning('Warning: criss-cross merge encountered. See bzr'
393
' help criss-cross.')
394
trace.mutter('Criss-cross lcas: %r' % lcas)
394
395
interesting_revision_ids = [self.base_rev_id]
395
396
interesting_revision_ids.extend(lcas)
396
397
interesting_trees = dict((t.get_revision_id(), t)
406
407
self.base_tree = self.revision_tree(self.base_rev_id)
407
408
self.base_is_ancestor = True
408
409
self.base_is_other_ancestor = True
409
mutter('Base revid: %r' % self.base_rev_id)
410
trace.mutter('Base revid: %r' % self.base_rev_id)
411
412
def set_base(self, base_revision):
412
413
"""Set the base revision to use for the merge.
414
415
:param base_revision: A 2-list containing a path and revision number.
416
mutter("doing merge() with no base_revision specified")
417
trace.mutter("doing merge() with no base_revision specified")
417
418
if base_revision == [None, None]:
439
440
if self.merge_type.supports_reprocess:
440
441
kwargs['reprocess'] = self.reprocess
441
442
elif self.reprocess:
442
raise BzrError("Conflict reduction is not supported for merge"
443
" type %s." % self.merge_type)
443
raise errors.BzrError(
444
"Conflict reduction is not supported for merge"
445
" type %s." % self.merge_type)
444
446
if self.merge_type.supports_show_base:
445
447
kwargs['show_base'] = self.show_base
446
448
elif self.show_base:
447
raise BzrError("Showing base is not supported for this"
448
" merge type. %s" % self.merge_type)
449
raise errors.BzrError("Showing base is not supported for this"
450
" merge type. %s" % self.merge_type)
449
451
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
450
452
and not self.base_is_other_ancestor):
451
453
raise errors.CannotReverseCherrypick()
501
503
self.this_tree.unlock()
502
504
if len(merge.cooked_conflicts) == 0:
503
if not self.ignore_zero and not is_quiet():
504
note("All changes applied successfully.")
505
if not self.ignore_zero and not trace.is_quiet():
506
trace.note("All changes applied successfully.")
506
note("%d conflicts encountered." % len(merge.cooked_conflicts))
508
trace.note("%d conflicts encountered."
509
% len(merge.cooked_conflicts))
508
511
return len(merge.cooked_conflicts)
539
542
def __init__(self, working_tree, this_tree, base_tree, other_tree,
540
543
interesting_ids=None, reprocess=False, show_base=False,
541
pb=DummyProgress(), pp=None, change_reporter=None,
544
pb=progress.DummyProgress(), pp=None, change_reporter=None,
542
545
interesting_files=None, do_merge=True,
543
546
cherrypick=False, lca_trees=None):
544
547
"""Initialize the merger object and perform the merge.
590
593
self.change_reporter = change_reporter
591
594
self.cherrypick = cherrypick
592
595
if self.pp is None:
593
self.pp = ProgressPhase("Merge phase", 3, self.pb)
596
self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
598
601
self.this_tree.lock_tree_write()
599
602
self.base_tree.lock_read()
600
603
self.other_tree.lock_read()
601
self.tt = TreeTransform(self.this_tree, self.pb)
604
self._compute_transform()
606
results = self.tt.apply(no_conflicts=True)
607
self.write_modified(results)
605
self.tt = transform.TreeTransform(self.this_tree, self.pb)
609
self.this_tree.add_conflicts(self.cooked_conflicts)
610
except UnsupportedOperation:
608
self._compute_transform()
610
results = self.tt.apply(no_conflicts=True)
611
self.write_modified(results)
613
self.this_tree.add_conflicts(self.cooked_conflicts)
614
except errors.UnsupportedOperation:
614
619
self.other_tree.unlock()
615
620
self.base_tree.unlock()
616
621
self.this_tree.unlock()
619
624
def make_preview_transform(self):
620
625
self.base_tree.lock_read()
621
626
self.other_tree.lock_read()
622
self.tt = TransformPreview(self.this_tree)
627
self.tt = transform.TransformPreview(self.this_tree)
624
629
self.pp.next_phase()
625
630
self._compute_transform()
655
660
self.pp.next_phase()
656
661
child_pb = ui.ui_factory.nested_progress_bar()
658
fs_conflicts = resolve_conflicts(self.tt, child_pb,
659
lambda t, c: conflict_pass(t, c, self.other_tree))
663
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
664
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
661
666
child_pb.finished()
662
667
if self.change_reporter is not None:
873
878
def fix_root(self):
875
880
self.tt.final_kind(self.tt.root)
881
except errors.NoSuchFile:
877
882
self.tt.cancel_deletion(self.tt.root)
878
883
if self.tt.final_file_id(self.tt.root) is None:
879
884
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1145
1150
self.tt.delete_contents(trans_id)
1146
1151
if file_id in self.other_tree:
1147
1152
# OTHER changed the file
1148
create_from_tree(self.tt, trans_id,
1149
self.other_tree, file_id)
1153
transform.create_from_tree(self.tt, trans_id,
1154
self.other_tree, file_id)
1150
1155
if not file_in_this:
1151
1156
self.tt.version_file(file_id, trans_id)
1152
1157
return "modified"
1163
1168
# have agreement that output should be a file.
1165
1170
self.text_merge(file_id, trans_id)
1171
except errors.BinaryFile:
1167
1172
return contents_conflict()
1168
1173
if file_id not in self.this_tree:
1169
1174
self.tt.version_file(file_id, trans_id)
1171
1176
self.tt.tree_kind(trans_id)
1172
1177
self.tt.delete_contents(trans_id)
1178
except errors.NoSuchFile:
1175
1180
return "modified"
1194
1199
base_lines = []
1195
1200
other_lines = self.get_lines(self.other_tree, file_id)
1196
1201
this_lines = self.get_lines(self.this_tree, file_id)
1197
m3 = Merge3(base_lines, this_lines, other_lines,
1198
is_cherrypick=self.cherrypick)
1202
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1203
is_cherrypick=self.cherrypick)
1199
1204
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1200
1205
if self.show_base is True:
1201
1206
base_marker = '|' * 7
1256
1261
"""Emit a single conflict file."""
1257
1262
name = name + '.' + suffix
1258
1263
trans_id = self.tt.create_path(name, parent_id)
1259
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1264
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines)
1260
1265
return trans_id
1262
1267
def merge_executable(self, file_id, file_status):
1304
1309
def cook_conflicts(self, fs_conflicts):
1305
1310
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1306
from conflicts import Conflict
1307
1311
name_conflicts = {}
1308
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1309
fp = FinalPaths(self.tt)
1312
self.cooked_conflicts.extend(transform.cook_conflicts(
1313
fs_conflicts, self.tt))
1314
fp = transform.FinalPaths(self.tt)
1310
1315
for conflict in self._raw_conflicts:
1311
1316
conflict_type = conflict[0]
1312
1317
if conflict_type in ('name conflict', 'parent conflict'):
1314
1319
conflict_args = conflict[2:]
1315
1320
if trans_id not in name_conflicts:
1316
1321
name_conflicts[trans_id] = {}
1317
unique_add(name_conflicts[trans_id], conflict_type,
1322
transform.unique_add(name_conflicts[trans_id], conflict_type,
1319
1324
if conflict_type == 'contents conflict':
1320
1325
for trans_id in conflict[1]:
1321
1326
file_id = self.tt.final_file_id(trans_id)
1326
1331
if path.endswith(suffix):
1327
1332
path = path[:-len(suffix)]
1329
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1334
c = _mod_conflicts.Conflict.factory(conflict_type,
1335
path=path, file_id=file_id)
1330
1336
self.cooked_conflicts.append(c)
1331
1337
if conflict_type == 'text conflict':
1332
1338
trans_id = conflict[1]
1333
1339
path = fp.get_path(trans_id)
1334
1340
file_id = self.tt.final_file_id(trans_id)
1335
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1341
c = _mod_conflicts.Conflict.factory(conflict_type,
1342
path=path, file_id=file_id)
1336
1343
self.cooked_conflicts.append(c)
1338
1345
for trans_id, conflicts in name_conflicts.iteritems():
1353
1360
if this_parent is not None and this_name is not None:
1354
1361
this_parent_path = \
1355
1362
fp.get_path(self.tt.trans_id_file_id(this_parent))
1356
this_path = pathjoin(this_parent_path, this_name)
1363
this_path = osutils.pathjoin(this_parent_path, this_name)
1358
1365
this_path = "<deleted>"
1359
1366
file_id = self.tt.final_file_id(trans_id)
1360
c = Conflict.factory('path conflict', path=this_path,
1361
conflict_path=other_path, file_id=file_id)
1367
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1368
conflict_path=other_path,
1362
1370
self.cooked_conflicts.append(c)
1363
self.cooked_conflicts.sort(key=Conflict.sort_key)
1371
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1366
1374
class WeaveMerger(Merge3Merger):
1387
1395
name = self.tt.final_name(trans_id) + '.plan'
1388
1396
contents = ('%10s|%s' % l for l in plan)
1389
1397
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1390
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1391
'>>>>>>> MERGE-SOURCE\n')
1398
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1399
'>>>>>>> MERGE-SOURCE\n')
1392
1400
return textmerge.merge_lines(self.reprocess)
1394
1402
def text_merge(self, file_id, trans_id):
1400
1408
lines = list(lines)
1401
1409
# Note we're checking whether the OUTPUT is binary in this case,
1402
1410
# because we don't want to get into weave merge guts.
1403
check_text_lines(lines)
1411
textfile.check_text_lines(lines)
1404
1412
self.tt.create_file(lines, trans_id)
1406
1414
self._raw_conflicts.append(('text conflict', trans_id))
1430
1438
name = self.tt.final_name(trans_id) + '.plan'
1431
1439
contents = ('%10s|%s' % l for l in plan)
1432
1440
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1433
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1434
'>>>>>>> MERGE-SOURCE\n')
1441
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1442
'>>>>>>> MERGE-SOURCE\n')
1435
1443
return textmerge.merge_lines(self.reprocess)
1439
1447
"""Three-way merger using external diff3 for text merging"""
1441
1449
def dump_file(self, temp_dir, name, tree, file_id):
1442
out_path = pathjoin(temp_dir, name)
1450
out_path = osutils.pathjoin(temp_dir, name)
1443
1451
out_file = open(out_path, "wb")
1445
1453
in_file = tree.get_file(file_id)
1457
1465
import bzrlib.patch
1458
1466
temp_dir = osutils.mkdtemp(prefix="bzr-")
1460
new_file = pathjoin(temp_dir, "new")
1468
new_file = osutils.pathjoin(temp_dir, "new")
1461
1469
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1462
1470
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1463
1471
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1464
1472
status = bzrlib.patch.diff3(new_file, this, base, other)
1465
1473
if status not in (0, 1):
1466
raise BzrError("Unhandled diff3 exit code")
1474
raise errors.BzrError("Unhandled diff3 exit code")
1467
1475
f = open(new_file, 'rb')
1469
1477
self.tt.create_file(f, trans_id)
1496
1504
branch.get_revision_tree(base_revision))'
1498
1506
if this_tree is None:
1499
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1500
"parameter as of bzrlib version 0.8.")
1507
raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1508
"parameter as of bzrlib version 0.8.")
1501
1509
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1502
1510
pb=pb, change_reporter=change_reporter)
1503
1511
merger.backup_files = backup_files
1516
1524
get_revision_id = getattr(base_tree, 'get_revision_id', None)
1517
1525
if get_revision_id is None:
1518
1526
get_revision_id = base_tree.last_revision
1527
merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1519
1528
merger.set_base_revision(get_revision_id(), this_branch)
1520
1529
return merger.do_merge()
1720
1729
super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1721
1730
self.a_key = self._key_prefix + (self.a_rev,)
1722
1731
self.b_key = self._key_prefix + (self.b_rev,)
1723
self.graph = Graph(self.vf)
1732
self.graph = _mod_graph.Graph(self.vf)
1724
1733
heads = self.graph.heads((self.a_key, self.b_key))
1725
1734
if len(heads) == 1:
1726
1735
# one side dominates, so we can just return its values, yay for
1734
mutter('found dominating revision for %s\n%s > %s', self.vf,
1735
self._head_key[-1], other)
1743
trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
1744
self._head_key[-1], other)
1736
1745
self._weave = None
1738
1747
self._head_key = None
1753
1762
next_lcas = self.graph.find_lca(*cur_ancestors)
1754
1763
# Map a plain NULL_REVISION to a simple no-ancestors
1755
if next_lcas == set([NULL_REVISION]):
1764
if next_lcas == set([_mod_revision.NULL_REVISION]):
1757
1766
# Order the lca's based on when they were merged into the tip
1758
1767
# While the actual merge portion of weave merge uses a set() of
1770
1779
elif len(next_lcas) > 2:
1771
1780
# More than 2 lca's, fall back to grabbing all nodes between
1772
1781
# this and the unique lca.
1773
mutter('More than 2 LCAs, falling back to all nodes for:'
1774
' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
1782
trace.mutter('More than 2 LCAs, falling back to all nodes for:'
1784
self.a_key, self.b_key, cur_ancestors)
1775
1785
cur_lcas = next_lcas
1776
1786
while len(cur_lcas) > 1:
1777
1787
cur_lcas = self.graph.find_lca(*cur_lcas)
1780
1790
unique_lca = None
1782
1792
unique_lca = list(cur_lcas)[0]
1783
if unique_lca == NULL_REVISION:
1793
if unique_lca == _mod_revision.NULL_REVISION:
1784
1794
# find_lca will return a plain 'NULL_REVISION' rather
1785
1795
# than a key tuple when there is no common ancestor, we
1786
1796
# prefer to just use None, because it doesn't confuse
1809
1819
# We remove NULL_REVISION because it isn't a proper tuple key, and
1810
1820
# thus confuses things like _get_interesting_texts, and our logic
1811
1821
# to add the texts into the memory weave.
1812
if NULL_REVISION in parent_map:
1813
parent_map.pop(NULL_REVISION)
1822
if _mod_revision.NULL_REVISION in parent_map:
1823
parent_map.pop(_mod_revision.NULL_REVISION)
1815
1825
interesting = set()
1816
1826
for tip in tip_keys: