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.
613
600
self.this_tree.lock_tree_write()
614
601
self.base_tree.lock_read()
615
602
self.other_tree.lock_read()
616
self.tt = TreeTransform(self.this_tree, self.pb)
619
self._compute_transform()
621
results = self.tt.apply(no_conflicts=True)
622
self.write_modified(results)
604
self.tt = transform.TreeTransform(self.this_tree, self.pb)
624
self.this_tree.add_conflicts(self.cooked_conflicts)
625
except UnsupportedOperation:
607
self._compute_transform()
609
results = self.tt.apply(no_conflicts=True)
610
self.write_modified(results)
612
self.this_tree.add_conflicts(self.cooked_conflicts)
613
except errors.UnsupportedOperation:
629
618
self.other_tree.unlock()
630
619
self.base_tree.unlock()
631
620
self.this_tree.unlock()
1160
1149
self.tt.delete_contents(trans_id)
1161
1150
if file_id in self.other_tree:
1162
1151
# OTHER changed the file
1163
create_from_tree(self.tt, trans_id,
1164
self.other_tree, file_id)
1153
if wt.supports_content_filtering():
1154
# We get the path from the working tree if it exists.
1155
# That fails though when OTHER is adding a file, so
1156
# we fall back to the other tree to find the path if
1157
# it doesn't exist locally.
1159
filter_tree_path = wt.id2path(file_id)
1160
except errors.NoSuchId:
1161
filter_tree_path = self.other_tree.id2path(file_id)
1163
# Skip the id2path lookup for older formats
1164
filter_tree_path = None
1165
transform.create_from_tree(self.tt, trans_id,
1166
self.other_tree, file_id,
1167
filter_tree_path=filter_tree_path)
1165
1168
if not file_in_this:
1166
1169
self.tt.version_file(file_id, trans_id)
1167
1170
return "modified"
1254
1257
('THIS', self.this_tree, this_lines)]
1255
1258
if not no_base:
1256
1259
data.append(('BASE', self.base_tree, base_lines))
1261
# We need to use the actual path in the working tree of the file here,
1262
# ignoring the conflict suffixes
1264
if wt.supports_content_filtering():
1266
filter_tree_path = wt.id2path(file_id)
1267
except errors.NoSuchId:
1268
# file has been deleted
1269
filter_tree_path = None
1271
# Skip the id2path lookup for older formats
1272
filter_tree_path = None
1257
1274
versioned = False
1258
1275
file_group = []
1259
1276
for suffix, tree, lines in data:
1260
1277
if file_id in tree:
1261
1278
trans_id = self._conflict_file(name, parent_id, tree, file_id,
1279
suffix, lines, filter_tree_path)
1263
1280
file_group.append(trans_id)
1264
1281
if set_version and not versioned:
1265
1282
self.tt.version_file(file_id, trans_id)
1267
1284
return file_group
1269
1286
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1287
lines=None, filter_tree_path=None):
1271
1288
"""Emit a single conflict file."""
1272
1289
name = name + '.' + suffix
1273
1290
trans_id = self.tt.create_path(name, parent_id)
1274
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1291
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
1275
1293
return trans_id
1277
1295
def merge_executable(self, file_id, file_status):
1341
1359
if path.endswith(suffix):
1342
1360
path = path[:-len(suffix)]
1344
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)
1345
1364
self.cooked_conflicts.append(c)
1346
1365
if conflict_type == 'text conflict':
1347
1366
trans_id = conflict[1]
1348
1367
path = fp.get_path(trans_id)
1349
1368
file_id = self.tt.final_file_id(trans_id)
1350
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)
1351
1371
self.cooked_conflicts.append(c)
1353
1373
for trans_id, conflicts in name_conflicts.iteritems():
1368
1388
if this_parent is not None and this_name is not None:
1369
1389
this_parent_path = \
1370
1390
fp.get_path(self.tt.trans_id_file_id(this_parent))
1371
this_path = pathjoin(this_parent_path, this_name)
1391
this_path = osutils.pathjoin(this_parent_path, this_name)
1373
1393
this_path = "<deleted>"
1374
1394
file_id = self.tt.final_file_id(trans_id)
1375
c = Conflict.factory('path conflict', path=this_path,
1376
conflict_path=other_path, file_id=file_id)
1395
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1396
conflict_path=other_path,
1377
1398
self.cooked_conflicts.append(c)
1378
self.cooked_conflicts.sort(key=Conflict.sort_key)
1399
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1381
1402
class WeaveMerger(Merge3Merger):
1385
1406
supports_reverse_cherrypick = False
1386
1407
history_based = True
1388
def _merged_lines(self, file_id):
1389
"""Generate the merged lines.
1390
There is no distinction between lines that are meant to contain <<<<<<<
1394
base = self.base_tree
1397
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)
1399
1423
if 'merge' in debug.debug_flags:
1400
1424
plan = list(plan)
1401
1425
trans_id = self.tt.trans_id_file_id(file_id)
1402
1426
name = self.tt.final_name(trans_id) + '.plan'
1403
contents = ('%10s|%s' % l for l in plan)
1427
contents = ('%11s|%s' % l for l in plan)
1404
1428
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1405
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1406
'>>>>>>> MERGE-SOURCE\n')
1407
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
1409
1438
def text_merge(self, file_id, trans_id):
1410
1439
"""Perform a (weave) text merge for a given file and file-id.
1411
1440
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1412
1441
and a conflict will be noted.
1414
lines, conflicts = self._merged_lines(file_id)
1443
lines, base_lines = self._merged_lines(file_id)
1415
1444
lines = list(lines)
1416
1445
# Note we're checking whether the OUTPUT is binary in this case,
1417
1446
# because we don't want to get into weave merge guts.
1418
check_text_lines(lines)
1447
textfile.check_text_lines(lines)
1419
1448
self.tt.create_file(lines, trans_id)
1449
if base_lines is not None:
1421
1451
self._raw_conflicts.append(('text conflict', trans_id))
1422
1452
name = self.tt.final_name(trans_id)
1423
1453
parent_id = self.tt.final_parent(trans_id)
1424
1454
file_group = self._dump_conflicts(name, parent_id, file_id,
1456
base_lines=base_lines)
1426
1457
file_group.append(trans_id)
1429
1460
class LCAMerger(WeaveMerger):
1431
def _merged_lines(self, file_id):
1432
"""Generate the merged lines.
1433
There is no distinction between lines that are meant to contain <<<<<<<
1437
base = self.base_tree
1440
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,
1442
if 'merge' in debug.debug_flags:
1444
trans_id = self.tt.trans_id_file_id(file_id)
1445
name = self.tt.final_name(trans_id) + '.plan'
1446
contents = ('%10s|%s' % l for l in plan)
1447
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1448
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1449
'>>>>>>> MERGE-SOURCE\n')
1450
return textmerge.merge_lines(self.reprocess)
1453
1466
class Diff3Merger(Merge3Merger):
1454
1467
"""Three-way merger using external diff3 for text merging"""
1456
1469
def dump_file(self, temp_dir, name, tree, file_id):
1457
out_path = pathjoin(temp_dir, name)
1470
out_path = osutils.pathjoin(temp_dir, name)
1458
1471
out_file = open(out_path, "wb")
1460
1473
in_file = tree.get_file(file_id)
1472
1485
import bzrlib.patch
1473
1486
temp_dir = osutils.mkdtemp(prefix="bzr-")
1475
new_file = pathjoin(temp_dir, "new")
1488
new_file = osutils.pathjoin(temp_dir, "new")
1476
1489
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1477
1490
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1478
1491
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1479
1492
status = bzrlib.patch.diff3(new_file, this, base, other)
1480
1493
if status not in (0, 1):
1481
raise BzrError("Unhandled diff3 exit code")
1494
raise errors.BzrError("Unhandled diff3 exit code")
1482
1495
f = open(new_file, 'rb')
1484
1497
self.tt.create_file(f, trans_id)