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
28
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
37
from bzrlib.symbol_versioning import (
63
41
# TODO: Report back as changes are merged in
242
220
if revno is None:
243
221
tree = workingtree.WorkingTree.open_containing(location)[0]
244
222
return tree.branch, tree
245
branch = Branch.open_containing(location, possible_transports)[0]
223
branch = _mod_branch.Branch.open_containing(
224
location, possible_transports)[0]
247
226
revision_id = branch.last_revision()
249
228
revision_id = branch.get_rev_id(revno)
250
revision_id = ensure_null(revision_id)
229
revision_id = _mod_revision.ensure_null(revision_id)
251
230
return branch, self.revision_tree(revision_id, branch)
232
@deprecated_method(deprecated_in((2, 1, 0)))
253
233
def ensure_revision_trees(self):
254
234
if self.this_revision_tree is None:
255
235
self.this_basis_tree = self.revision_tree(self.this_basis)
271
252
if self.this_rev_id is None:
272
253
if self.this_basis_tree.get_file_sha1(file_id) != \
273
254
self.this_tree.get_file_sha1(file_id):
274
raise WorkingTreeNotRevision(self.this_tree)
255
raise errors.WorkingTreeNotRevision(self.this_tree)
276
257
trees = (self.this_basis_tree, self.other_tree)
277
258
return [get_id(tree, file_id) for tree in trees]
260
@deprecated_method(deprecated_in((2, 1, 0)))
279
261
def check_basis(self, check_clean, require_commits=True):
280
262
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')")
263
raise errors.BzrCommandError(
264
"This branch has no commits."
265
" (perhaps you would prefer 'bzr pull')")
284
267
self.compare_basis()
285
268
if self.this_basis != self.this_rev_id:
286
269
raise errors.UncommittedChanges(self.this_tree)
271
@deprecated_method(deprecated_in((2, 1, 0)))
288
272
def compare_basis(self):
290
274
basis_tree = self.revision_tree(self.this_tree.last_revision())
375
360
target.fetch(source, revision_id)
377
362
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
363
revisions = [_mod_revision.ensure_null(self.this_basis),
364
_mod_revision.ensure_null(self.other_basis)]
365
if _mod_revision.NULL_REVISION in revisions:
366
self.base_rev_id = _mod_revision.NULL_REVISION
382
367
self.base_tree = self.revision_tree(self.base_rev_id)
383
368
self._is_criss_cross = False
385
370
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
386
371
self._is_criss_cross = False
387
372
if len(lcas) == 0:
388
self.base_rev_id = NULL_REVISION
373
self.base_rev_id = _mod_revision.NULL_REVISION
389
374
elif len(lcas) == 1:
390
375
self.base_rev_id = list(lcas)[0]
391
376
else: # len(lcas) > 1
400
385
self.base_rev_id = self.revision_graph.find_unique_lca(
402
387
self._is_criss_cross = True
403
if self.base_rev_id == NULL_REVISION:
404
raise UnrelatedBranches()
388
if self.base_rev_id == _mod_revision.NULL_REVISION:
389
raise errors.UnrelatedBranches()
405
390
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)
391
trace.warning('Warning: criss-cross merge encountered. See bzr'
392
' help criss-cross.')
393
trace.mutter('Criss-cross lcas: %r' % lcas)
409
394
interesting_revision_ids = [self.base_rev_id]
410
395
interesting_revision_ids.extend(lcas)
411
396
interesting_trees = dict((t.get_revision_id(), t)
454
439
if self.merge_type.supports_reprocess:
455
440
kwargs['reprocess'] = self.reprocess
456
441
elif self.reprocess:
457
raise BzrError("Conflict reduction is not supported for merge"
458
" type %s." % self.merge_type)
442
raise errors.BzrError(
443
"Conflict reduction is not supported for merge"
444
" type %s." % self.merge_type)
459
445
if self.merge_type.supports_show_base:
460
446
kwargs['show_base'] = self.show_base
461
447
elif self.show_base:
462
raise BzrError("Showing base is not supported for this"
463
" merge type. %s" % self.merge_type)
448
raise errors.BzrError("Showing base is not supported for this"
449
" merge type. %s" % self.merge_type)
464
450
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
465
451
and not self.base_is_other_ancestor):
466
452
raise errors.CannotReverseCherrypick()
554
541
def __init__(self, working_tree, this_tree, base_tree, other_tree,
555
542
interesting_ids=None, reprocess=False, show_base=False,
556
pb=DummyProgress(), pp=None, change_reporter=None,
543
pb=progress.DummyProgress(), pp=None, change_reporter=None,
557
544
interesting_files=None, do_merge=True,
558
545
cherrypick=False, lca_trees=None):
559
546
"""Initialize the merger object and perform the merge.
1372
1359
if path.endswith(suffix):
1373
1360
path = path[:-len(suffix)]
1375
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1362
c = _mod_conflicts.Conflict.factory(conflict_type,
1363
path=path, file_id=file_id)
1376
1364
self.cooked_conflicts.append(c)
1377
1365
if conflict_type == 'text conflict':
1378
1366
trans_id = conflict[1]
1379
1367
path = fp.get_path(trans_id)
1380
1368
file_id = self.tt.final_file_id(trans_id)
1381
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1369
c = _mod_conflicts.Conflict.factory(conflict_type,
1370
path=path, file_id=file_id)
1382
1371
self.cooked_conflicts.append(c)
1384
1373
for trans_id, conflicts in name_conflicts.iteritems():
1399
1388
if this_parent is not None and this_name is not None:
1400
1389
this_parent_path = \
1401
1390
fp.get_path(self.tt.trans_id_file_id(this_parent))
1402
this_path = pathjoin(this_parent_path, this_name)
1391
this_path = osutils.pathjoin(this_parent_path, this_name)
1404
1393
this_path = "<deleted>"
1405
1394
file_id = self.tt.final_file_id(trans_id)
1406
c = Conflict.factory('path conflict', path=this_path,
1407
conflict_path=other_path, file_id=file_id)
1395
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1396
conflict_path=other_path,
1408
1398
self.cooked_conflicts.append(c)
1409
self.cooked_conflicts.sort(key=Conflict.sort_key)
1399
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1412
1402
class WeaveMerger(Merge3Merger):
1416
1406
supports_reverse_cherrypick = False
1417
1407
history_based = True
1419
def _merged_lines(self, file_id):
1420
"""Generate the merged lines.
1421
There is no distinction between lines that are meant to contain <<<<<<<
1425
base = self.base_tree
1428
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1409
def _generate_merge_plan(self, file_id, base):
1410
return self.this_tree.plan_file_merge(file_id, self.other_tree,
1413
def _merged_lines(self, file_id):
1414
"""Generate the merged lines.
1415
There is no distinction between lines that are meant to contain <<<<<<<
1419
base = self.base_tree
1422
plan = self._generate_merge_plan(file_id, base)
1430
1423
if 'merge' in debug.debug_flags:
1431
1424
plan = list(plan)
1432
1425
trans_id = self.tt.trans_id_file_id(file_id)
1433
1426
name = self.tt.final_name(trans_id) + '.plan'
1434
contents = ('%10s|%s' % l for l in plan)
1427
contents = ('%11s|%s' % l for l in plan)
1435
1428
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1436
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1437
'>>>>>>> MERGE-SOURCE\n')
1438
return textmerge.merge_lines(self.reprocess)
1429
textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1430
'>>>>>>> MERGE-SOURCE\n')
1431
lines, conflicts = textmerge.merge_lines(self.reprocess)
1433
base_lines = textmerge.base_from_plan()
1436
return lines, base_lines
1440
1438
def text_merge(self, file_id, trans_id):
1441
1439
"""Perform a (weave) text merge for a given file and file-id.
1442
1440
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1443
1441
and a conflict will be noted.
1445
lines, conflicts = self._merged_lines(file_id)
1443
lines, base_lines = self._merged_lines(file_id)
1446
1444
lines = list(lines)
1447
1445
# Note we're checking whether the OUTPUT is binary in this case,
1448
1446
# because we don't want to get into weave merge guts.
1449
check_text_lines(lines)
1447
textfile.check_text_lines(lines)
1450
1448
self.tt.create_file(lines, trans_id)
1449
if base_lines is not None:
1452
1451
self._raw_conflicts.append(('text conflict', trans_id))
1453
1452
name = self.tt.final_name(trans_id)
1454
1453
parent_id = self.tt.final_parent(trans_id)
1455
1454
file_group = self._dump_conflicts(name, parent_id, file_id,
1456
base_lines=base_lines)
1457
1457
file_group.append(trans_id)
1460
1460
class LCAMerger(WeaveMerger):
1462
def _merged_lines(self, file_id):
1463
"""Generate the merged lines.
1464
There is no distinction between lines that are meant to contain <<<<<<<
1468
base = self.base_tree
1471
plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1462
def _generate_merge_plan(self, file_id, base):
1463
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1473
if 'merge' in debug.debug_flags:
1475
trans_id = self.tt.trans_id_file_id(file_id)
1476
name = self.tt.final_name(trans_id) + '.plan'
1477
contents = ('%10s|%s' % l for l in plan)
1478
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1479
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1480
'>>>>>>> MERGE-SOURCE\n')
1481
return textmerge.merge_lines(self.reprocess)
1484
1466
class Diff3Merger(Merge3Merger):
1485
1467
"""Three-way merger using external diff3 for text merging"""
1487
1469
def dump_file(self, temp_dir, name, tree, file_id):
1488
out_path = pathjoin(temp_dir, name)
1470
out_path = osutils.pathjoin(temp_dir, name)
1489
1471
out_file = open(out_path, "wb")
1491
1473
in_file = tree.get_file(file_id)
1503
1485
import bzrlib.patch
1504
1486
temp_dir = osutils.mkdtemp(prefix="bzr-")
1506
new_file = pathjoin(temp_dir, "new")
1488
new_file = osutils.pathjoin(temp_dir, "new")
1507
1489
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1508
1490
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1509
1491
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1510
1492
status = bzrlib.patch.diff3(new_file, this, base, other)
1511
1493
if status not in (0, 1):
1512
raise BzrError("Unhandled diff3 exit code")
1494
raise errors.BzrError("Unhandled diff3 exit code")
1513
1495
f = open(new_file, 'rb')
1515
1497
self.tt.create_file(f, trans_id)