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
184
163
base_revision_id, tree.branch.last_revision())):
185
164
base_revision_id = None
187
warning('Performing cherrypick')
166
trace.warning('Performing cherrypick')
188
167
merger = klass.from_revision_ids(pb, tree, other_revision_id,
189
168
base_revision_id, revision_graph=
242
221
if revno is None:
243
222
tree = workingtree.WorkingTree.open_containing(location)[0]
244
223
return tree.branch, tree
245
branch = Branch.open_containing(location, possible_transports)[0]
224
branch = _mod_branch.Branch.open_containing(
225
location, possible_transports)[0]
247
227
revision_id = branch.last_revision()
249
229
revision_id = branch.get_rev_id(revno)
250
revision_id = ensure_null(revision_id)
230
revision_id = _mod_revision.ensure_null(revision_id)
251
231
return branch, self.revision_tree(revision_id, branch)
233
@deprecated_method(deprecated_in((2, 1, 0)))
253
234
def ensure_revision_trees(self):
254
235
if self.this_revision_tree is None:
255
236
self.this_basis_tree = self.revision_tree(self.this_basis)
263
244
other_rev_id = self.other_basis
264
245
self.other_tree = other_basis_tree
247
@deprecated_method(deprecated_in((2, 1, 0)))
266
248
def file_revisions(self, file_id):
267
249
self.ensure_revision_trees()
268
250
def get_id(tree, file_id):
271
253
if self.this_rev_id is None:
272
254
if self.this_basis_tree.get_file_sha1(file_id) != \
273
255
self.this_tree.get_file_sha1(file_id):
274
raise WorkingTreeNotRevision(self.this_tree)
256
raise errors.WorkingTreeNotRevision(self.this_tree)
276
258
trees = (self.this_basis_tree, self.other_tree)
277
259
return [get_id(tree, file_id) for tree in trees]
261
@deprecated_method(deprecated_in((2, 1, 0)))
279
262
def check_basis(self, check_clean, require_commits=True):
280
263
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')")
264
raise errors.BzrCommandError(
265
"This branch has no commits."
266
" (perhaps you would prefer 'bzr pull')")
284
268
self.compare_basis()
285
269
if self.this_basis != self.this_rev_id:
286
270
raise errors.UncommittedChanges(self.this_tree)
272
@deprecated_method(deprecated_in((2, 1, 0)))
288
273
def compare_basis(self):
290
275
basis_tree = self.revision_tree(self.this_tree.last_revision())
297
282
self.interesting_files = file_list
299
284
def set_pending(self):
300
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
285
if (not self.base_is_ancestor or not self.base_is_other_ancestor
286
or self.other_rev_id is None):
302
288
self._add_parent()
333
319
self.other_rev_id = _mod_revision.ensure_null(
334
320
self.other_branch.last_revision())
335
321
if _mod_revision.is_null(self.other_rev_id):
336
raise NoCommits(self.other_branch)
322
raise errors.NoCommits(self.other_branch)
337
323
self.other_basis = self.other_rev_id
338
324
elif other_revision[1] is not None:
339
325
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
342
328
self.other_rev_id = None
343
329
self.other_basis = self.other_branch.last_revision()
344
330
if self.other_basis is None:
345
raise NoCommits(self.other_branch)
331
raise errors.NoCommits(self.other_branch)
346
332
if self.other_rev_id is not None:
347
333
self._cached_trees[self.other_rev_id] = self.other_tree
348
334
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
375
361
target.fetch(source, revision_id)
377
363
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
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
382
368
self.base_tree = self.revision_tree(self.base_rev_id)
383
369
self._is_criss_cross = False
385
371
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
386
372
self._is_criss_cross = False
387
373
if len(lcas) == 0:
388
self.base_rev_id = NULL_REVISION
374
self.base_rev_id = _mod_revision.NULL_REVISION
389
375
elif len(lcas) == 1:
390
376
self.base_rev_id = list(lcas)[0]
391
377
else: # len(lcas) > 1
400
386
self.base_rev_id = self.revision_graph.find_unique_lca(
402
388
self._is_criss_cross = True
403
if self.base_rev_id == NULL_REVISION:
404
raise UnrelatedBranches()
389
if self.base_rev_id == _mod_revision.NULL_REVISION:
390
raise errors.UnrelatedBranches()
405
391
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)
392
trace.warning('Warning: criss-cross merge encountered. See bzr'
393
' help criss-cross.')
394
trace.mutter('Criss-cross lcas: %r' % lcas)
409
395
interesting_revision_ids = [self.base_rev_id]
410
396
interesting_revision_ids.extend(lcas)
411
397
interesting_trees = dict((t.get_revision_id(), t)
421
407
self.base_tree = self.revision_tree(self.base_rev_id)
422
408
self.base_is_ancestor = True
423
409
self.base_is_other_ancestor = True
424
mutter('Base revid: %r' % self.base_rev_id)
410
trace.mutter('Base revid: %r' % self.base_rev_id)
426
412
def set_base(self, base_revision):
427
413
"""Set the base revision to use for the merge.
429
415
:param base_revision: A 2-list containing a path and revision number.
431
mutter("doing merge() with no base_revision specified")
417
trace.mutter("doing merge() with no base_revision specified")
432
418
if base_revision == [None, None]:
454
440
if self.merge_type.supports_reprocess:
455
441
kwargs['reprocess'] = self.reprocess
456
442
elif self.reprocess:
457
raise BzrError("Conflict reduction is not supported for merge"
458
" type %s." % self.merge_type)
443
raise errors.BzrError(
444
"Conflict reduction is not supported for merge"
445
" type %s." % self.merge_type)
459
446
if self.merge_type.supports_show_base:
460
447
kwargs['show_base'] = self.show_base
461
448
elif self.show_base:
462
raise BzrError("Showing base is not supported for this"
463
" merge type. %s" % self.merge_type)
449
raise errors.BzrError("Showing base is not supported for this"
450
" merge type. %s" % self.merge_type)
464
451
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
465
452
and not self.base_is_other_ancestor):
466
453
raise errors.CannotReverseCherrypick()
516
503
self.this_tree.unlock()
517
504
if len(merge.cooked_conflicts) == 0:
518
if not self.ignore_zero and not is_quiet():
519
note("All changes applied successfully.")
505
if not self.ignore_zero and not trace.is_quiet():
506
trace.note("All changes applied successfully.")
521
note("%d conflicts encountered." % len(merge.cooked_conflicts))
508
trace.note("%d conflicts encountered."
509
% len(merge.cooked_conflicts))
523
511
return len(merge.cooked_conflicts)
554
542
def __init__(self, working_tree, this_tree, base_tree, other_tree,
555
543
interesting_ids=None, reprocess=False, show_base=False,
556
pb=DummyProgress(), pp=None, change_reporter=None,
544
pb=progress.DummyProgress(), pp=None, change_reporter=None,
557
545
interesting_files=None, do_merge=True,
558
546
cherrypick=False, lca_trees=None):
559
547
"""Initialize the merger object and perform the merge.
605
593
self.change_reporter = change_reporter
606
594
self.cherrypick = cherrypick
607
595
if self.pp is None:
608
self.pp = ProgressPhase("Merge phase", 3, self.pb)
596
self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
614
602
self.base_tree.lock_read()
615
603
self.other_tree.lock_read()
617
self.tt = TreeTransform(self.this_tree, self.pb)
605
self.tt = transform.TreeTransform(self.this_tree, self.pb)
619
607
self.pp.next_phase()
620
608
self._compute_transform()
636
624
def make_preview_transform(self):
637
625
self.base_tree.lock_read()
638
626
self.other_tree.lock_read()
639
self.tt = TransformPreview(self.this_tree)
627
self.tt = transform.TransformPreview(self.this_tree)
641
629
self.pp.next_phase()
642
630
self._compute_transform()
672
660
self.pp.next_phase()
673
661
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))
663
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
664
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
678
666
child_pb.finished()
679
667
if self.change_reporter is not None:
682
670
self.tt.iter_changes(), self.change_reporter)
683
671
self.cook_conflicts(fs_conflicts)
684
672
for conflict in self.cooked_conflicts:
673
trace.warning(conflict)
687
675
def _entries3(self):
688
676
"""Gather data about files modified between three trees.
890
878
def fix_root(self):
892
880
self.tt.final_kind(self.tt.root)
881
except errors.NoSuchFile:
894
882
self.tt.cancel_deletion(self.tt.root)
895
883
if self.tt.final_file_id(self.tt.root) is None:
896
884
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1162
1150
self.tt.delete_contents(trans_id)
1163
1151
if file_id in self.other_tree:
1164
1152
# OTHER changed the file
1165
create_from_tree(self.tt, trans_id,
1166
self.other_tree, file_id)
1153
transform.create_from_tree(self.tt, trans_id,
1154
self.other_tree, file_id)
1167
1155
if not file_in_this:
1168
1156
self.tt.version_file(file_id, trans_id)
1169
1157
return "modified"
1180
1168
# have agreement that output should be a file.
1182
1170
self.text_merge(file_id, trans_id)
1171
except errors.BinaryFile:
1184
1172
return contents_conflict()
1185
1173
if file_id not in self.this_tree:
1186
1174
self.tt.version_file(file_id, trans_id)
1188
1176
self.tt.tree_kind(trans_id)
1189
1177
self.tt.delete_contents(trans_id)
1178
except errors.NoSuchFile:
1192
1180
return "modified"
1211
1199
base_lines = []
1212
1200
other_lines = self.get_lines(self.other_tree, file_id)
1213
1201
this_lines = self.get_lines(self.this_tree, file_id)
1214
m3 = Merge3(base_lines, this_lines, other_lines,
1215
is_cherrypick=self.cherrypick)
1202
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1203
is_cherrypick=self.cherrypick)
1216
1204
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1217
1205
if self.show_base is True:
1218
1206
base_marker = '|' * 7
1273
1261
"""Emit a single conflict file."""
1274
1262
name = name + '.' + suffix
1275
1263
trans_id = self.tt.create_path(name, parent_id)
1276
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1264
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines)
1277
1265
return trans_id
1279
1267
def merge_executable(self, file_id, file_status):
1321
1309
def cook_conflicts(self, fs_conflicts):
1322
1310
"""Convert all conflicts into a form that doesn't depend on trans_id"""
1323
from conflicts import Conflict
1324
1311
name_conflicts = {}
1325
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1326
fp = FinalPaths(self.tt)
1312
self.cooked_conflicts.extend(transform.cook_conflicts(
1313
fs_conflicts, self.tt))
1314
fp = transform.FinalPaths(self.tt)
1327
1315
for conflict in self._raw_conflicts:
1328
1316
conflict_type = conflict[0]
1329
1317
if conflict_type in ('name conflict', 'parent conflict'):
1331
1319
conflict_args = conflict[2:]
1332
1320
if trans_id not in name_conflicts:
1333
1321
name_conflicts[trans_id] = {}
1334
unique_add(name_conflicts[trans_id], conflict_type,
1322
transform.unique_add(name_conflicts[trans_id], conflict_type,
1336
1324
if conflict_type == 'contents conflict':
1337
1325
for trans_id in conflict[1]:
1338
1326
file_id = self.tt.final_file_id(trans_id)
1343
1331
if path.endswith(suffix):
1344
1332
path = path[:-len(suffix)]
1346
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)
1347
1336
self.cooked_conflicts.append(c)
1348
1337
if conflict_type == 'text conflict':
1349
1338
trans_id = conflict[1]
1350
1339
path = fp.get_path(trans_id)
1351
1340
file_id = self.tt.final_file_id(trans_id)
1352
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)
1353
1343
self.cooked_conflicts.append(c)
1355
1345
for trans_id, conflicts in name_conflicts.iteritems():
1370
1360
if this_parent is not None and this_name is not None:
1371
1361
this_parent_path = \
1372
1362
fp.get_path(self.tt.trans_id_file_id(this_parent))
1373
this_path = pathjoin(this_parent_path, this_name)
1363
this_path = osutils.pathjoin(this_parent_path, this_name)
1375
1365
this_path = "<deleted>"
1376
1366
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)
1367
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1368
conflict_path=other_path,
1379
1370
self.cooked_conflicts.append(c)
1380
self.cooked_conflicts.sort(key=Conflict.sort_key)
1371
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1383
1374
class WeaveMerger(Merge3Merger):
1404
1395
name = self.tt.final_name(trans_id) + '.plan'
1405
1396
contents = ('%10s|%s' % l for l in plan)
1406
1397
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1407
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1408
'>>>>>>> MERGE-SOURCE\n')
1398
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1399
'>>>>>>> MERGE-SOURCE\n')
1409
1400
return textmerge.merge_lines(self.reprocess)
1411
1402
def text_merge(self, file_id, trans_id):
1417
1408
lines = list(lines)
1418
1409
# Note we're checking whether the OUTPUT is binary in this case,
1419
1410
# because we don't want to get into weave merge guts.
1420
check_text_lines(lines)
1411
textfile.check_text_lines(lines)
1421
1412
self.tt.create_file(lines, trans_id)
1423
1414
self._raw_conflicts.append(('text conflict', trans_id))
1447
1438
name = self.tt.final_name(trans_id) + '.plan'
1448
1439
contents = ('%10s|%s' % l for l in plan)
1449
1440
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1450
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1451
'>>>>>>> MERGE-SOURCE\n')
1441
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1442
'>>>>>>> MERGE-SOURCE\n')
1452
1443
return textmerge.merge_lines(self.reprocess)
1456
1447
"""Three-way merger using external diff3 for text merging"""
1458
1449
def dump_file(self, temp_dir, name, tree, file_id):
1459
out_path = pathjoin(temp_dir, name)
1450
out_path = osutils.pathjoin(temp_dir, name)
1460
1451
out_file = open(out_path, "wb")
1462
1453
in_file = tree.get_file(file_id)
1474
1465
import bzrlib.patch
1475
1466
temp_dir = osutils.mkdtemp(prefix="bzr-")
1477
new_file = pathjoin(temp_dir, "new")
1468
new_file = osutils.pathjoin(temp_dir, "new")
1478
1469
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1479
1470
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1480
1471
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1481
1472
status = bzrlib.patch.diff3(new_file, this, base, other)
1482
1473
if status not in (0, 1):
1483
raise BzrError("Unhandled diff3 exit code")
1474
raise errors.BzrError("Unhandled diff3 exit code")
1484
1475
f = open(new_file, 'rb')
1486
1477
self.tt.create_file(f, trans_id)
1513
1504
branch.get_revision_tree(base_revision))'
1515
1506
if this_tree is None:
1516
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1517
"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.")
1518
1509
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1519
1510
pb=pb, change_reporter=change_reporter)
1520
1511
merger.backup_files = backup_files
1738
1729
super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1739
1730
self.a_key = self._key_prefix + (self.a_rev,)
1740
1731
self.b_key = self._key_prefix + (self.b_rev,)
1741
self.graph = Graph(self.vf)
1732
self.graph = _mod_graph.Graph(self.vf)
1742
1733
heads = self.graph.heads((self.a_key, self.b_key))
1743
1734
if len(heads) == 1:
1744
1735
# 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)
1743
trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
1744
self._head_key[-1], other)
1754
1745
self._weave = None
1756
1747
self._head_key = None
1771
1762
next_lcas = self.graph.find_lca(*cur_ancestors)
1772
1763
# Map a plain NULL_REVISION to a simple no-ancestors
1773
if next_lcas == set([NULL_REVISION]):
1764
if next_lcas == set([_mod_revision.NULL_REVISION]):
1775
1766
# Order the lca's based on when they were merged into the tip
1776
1767
# While the actual merge portion of weave merge uses a set() of
1788
1779
elif len(next_lcas) > 2:
1789
1780
# More than 2 lca's, fall back to grabbing all nodes between
1790
1781
# 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)
1782
trace.mutter('More than 2 LCAs, falling back to all nodes for:'
1784
self.a_key, self.b_key, cur_ancestors)
1793
1785
cur_lcas = next_lcas
1794
1786
while len(cur_lcas) > 1:
1795
1787
cur_lcas = self.graph.find_lca(*cur_lcas)
1798
1790
unique_lca = None
1800
1792
unique_lca = list(cur_lcas)[0]
1801
if unique_lca == NULL_REVISION:
1793
if unique_lca == _mod_revision.NULL_REVISION:
1802
1794
# find_lca will return a plain 'NULL_REVISION' rather
1803
1795
# than a key tuple when there is no common ancestor, we
1804
1796
# prefer to just use None, because it doesn't confuse
1827
1819
# We remove NULL_REVISION because it isn't a proper tuple key, and
1828
1820
# thus confuses things like _get_interesting_texts, and our logic
1829
1821
# to add the texts into the memory weave.
1830
if NULL_REVISION in parent_map:
1831
parent_map.pop(NULL_REVISION)
1822
if _mod_revision.NULL_REVISION in parent_map:
1823
parent_map.pop(_mod_revision.NULL_REVISION)
1833
1825
interesting = set()
1834
1826
for tip in tip_keys: