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
66
44
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)
45
from_tree.lock_tree_write()
47
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
48
interesting_ids=interesting_ids, this_tree=from_tree)
71
53
class Merger(object):
227
220
if revno is None:
228
221
tree = workingtree.WorkingTree.open_containing(location)[0]
229
222
return tree.branch, tree
230
branch = Branch.open_containing(location, possible_transports)[0]
223
branch = _mod_branch.Branch.open_containing(
224
location, possible_transports)[0]
232
226
revision_id = branch.last_revision()
234
228
revision_id = branch.get_rev_id(revno)
235
revision_id = ensure_null(revision_id)
229
revision_id = _mod_revision.ensure_null(revision_id)
236
230
return branch, self.revision_tree(revision_id, branch)
232
@deprecated_method(deprecated_in((2, 1, 0)))
238
233
def ensure_revision_trees(self):
239
234
if self.this_revision_tree is None:
240
235
self.this_basis_tree = self.revision_tree(self.this_basis)
244
239
if self.other_rev_id is None:
245
240
other_basis_tree = self.revision_tree(self.other_basis)
246
changes = other_basis_tree.changes_from(self.other_tree)
247
if changes.has_changed():
248
raise WorkingTreeNotRevision(self.this_tree)
241
if other_basis_tree.has_changes(self.other_tree):
242
raise errors.WorkingTreeNotRevision(self.this_tree)
249
243
other_rev_id = self.other_basis
250
244
self.other_tree = other_basis_tree
246
@deprecated_method(deprecated_in((2, 1, 0)))
252
247
def file_revisions(self, file_id):
253
248
self.ensure_revision_trees()
254
249
def get_id(tree, file_id):
257
252
if self.this_rev_id is None:
258
253
if self.this_basis_tree.get_file_sha1(file_id) != \
259
254
self.this_tree.get_file_sha1(file_id):
260
raise WorkingTreeNotRevision(self.this_tree)
255
raise errors.WorkingTreeNotRevision(self.this_tree)
262
257
trees = (self.this_basis_tree, self.other_tree)
263
258
return [get_id(tree, file_id) for tree in trees]
260
@deprecated_method(deprecated_in((2, 1, 0)))
265
261
def check_basis(self, check_clean, require_commits=True):
266
262
if self.this_basis is None and require_commits is True:
267
raise BzrCommandError("This branch has no commits."
268
" (perhaps you would prefer 'bzr pull')")
263
raise errors.BzrCommandError(
264
"This branch has no commits."
265
" (perhaps you would prefer 'bzr pull')")
270
267
self.compare_basis()
271
268
if self.this_basis != self.this_rev_id:
272
269
raise errors.UncommittedChanges(self.this_tree)
271
@deprecated_method(deprecated_in((2, 1, 0)))
274
272
def compare_basis(self):
276
274
basis_tree = self.revision_tree(self.this_tree.last_revision())
277
275
except errors.NoSuchRevision:
278
276
basis_tree = self.this_tree.basis_tree()
279
changes = self.this_tree.changes_from(basis_tree)
280
if not changes.has_changed():
277
if not self.this_tree.has_changes(basis_tree):
281
278
self.this_rev_id = self.this_basis
283
280
def set_interesting_files(self, file_list):
284
281
self.interesting_files = file_list
286
283
def set_pending(self):
287
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
284
if (not self.base_is_ancestor or not self.base_is_other_ancestor
285
or self.other_rev_id is None):
289
287
self._add_parent()
362
360
target.fetch(source, revision_id)
364
362
def find_base(self):
365
revisions = [ensure_null(self.this_basis),
366
ensure_null(self.other_basis)]
367
if NULL_REVISION in revisions:
368
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
369
367
self.base_tree = self.revision_tree(self.base_rev_id)
370
368
self._is_criss_cross = False
372
370
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
373
371
self._is_criss_cross = False
374
372
if len(lcas) == 0:
375
self.base_rev_id = NULL_REVISION
373
self.base_rev_id = _mod_revision.NULL_REVISION
376
374
elif len(lcas) == 1:
377
375
self.base_rev_id = list(lcas)[0]
378
376
else: # len(lcas) > 1
387
385
self.base_rev_id = self.revision_graph.find_unique_lca(
389
387
self._is_criss_cross = True
390
if self.base_rev_id == NULL_REVISION:
391
raise UnrelatedBranches()
388
if self.base_rev_id == _mod_revision.NULL_REVISION:
389
raise errors.UnrelatedBranches()
392
390
if self._is_criss_cross:
393
warning('Warning: criss-cross merge encountered. See bzr'
394
' help criss-cross.')
395
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)
396
394
interesting_revision_ids = [self.base_rev_id]
397
395
interesting_revision_ids.extend(lcas)
398
396
interesting_trees = dict((t.get_revision_id(), t)
441
439
if self.merge_type.supports_reprocess:
442
440
kwargs['reprocess'] = self.reprocess
443
441
elif self.reprocess:
444
raise BzrError("Conflict reduction is not supported for merge"
445
" type %s." % self.merge_type)
442
raise errors.BzrError(
443
"Conflict reduction is not supported for merge"
444
" type %s." % self.merge_type)
446
445
if self.merge_type.supports_show_base:
447
446
kwargs['show_base'] = self.show_base
448
447
elif self.show_base:
449
raise BzrError("Showing base is not supported for this"
450
" merge type. %s" % self.merge_type)
448
raise errors.BzrError("Showing base is not supported for this"
449
" merge type. %s" % self.merge_type)
451
450
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
452
451
and not self.base_is_other_ancestor):
453
452
raise errors.CannotReverseCherrypick()
541
541
def __init__(self, working_tree, this_tree, base_tree, other_tree,
542
542
interesting_ids=None, reprocess=False, show_base=False,
543
pb=DummyProgress(), pp=None, change_reporter=None,
543
pb=progress.DummyProgress(), pp=None, change_reporter=None,
544
544
interesting_files=None, do_merge=True,
545
545
cherrypick=False, lca_trees=None):
546
546
"""Initialize the merger object and perform the merge.
600
600
self.this_tree.lock_tree_write()
601
601
self.base_tree.lock_read()
602
602
self.other_tree.lock_read()
603
self.tt = TreeTransform(self.this_tree, self.pb)
606
self._compute_transform()
608
results = self.tt.apply(no_conflicts=True)
609
self.write_modified(results)
604
self.tt = transform.TreeTransform(self.this_tree, self.pb)
611
self.this_tree.add_conflicts(self.cooked_conflicts)
612
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:
616
618
self.other_tree.unlock()
617
619
self.base_tree.unlock()
618
620
self.this_tree.unlock()
1147
1149
self.tt.delete_contents(trans_id)
1148
1150
if file_id in self.other_tree:
1149
1151
# OTHER changed the file
1150
create_from_tree(self.tt, trans_id,
1151
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)
1152
1168
if not file_in_this:
1153
1169
self.tt.version_file(file_id, trans_id)
1154
1170
return "modified"
1241
1257
('THIS', self.this_tree, this_lines)]
1242
1258
if not no_base:
1243
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
1244
1274
versioned = False
1245
1275
file_group = []
1246
1276
for suffix, tree, lines in data:
1247
1277
if file_id in tree:
1248
1278
trans_id = self._conflict_file(name, parent_id, tree, file_id,
1279
suffix, lines, filter_tree_path)
1250
1280
file_group.append(trans_id)
1251
1281
if set_version and not versioned:
1252
1282
self.tt.version_file(file_id, trans_id)
1254
1284
return file_group
1256
1286
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1287
lines=None, filter_tree_path=None):
1258
1288
"""Emit a single conflict file."""
1259
1289
name = name + '.' + suffix
1260
1290
trans_id = self.tt.create_path(name, parent_id)
1261
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1291
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
1262
1293
return trans_id
1264
1295
def merge_executable(self, file_id, file_status):
1328
1359
if path.endswith(suffix):
1329
1360
path = path[:-len(suffix)]
1331
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)
1332
1364
self.cooked_conflicts.append(c)
1333
1365
if conflict_type == 'text conflict':
1334
1366
trans_id = conflict[1]
1335
1367
path = fp.get_path(trans_id)
1336
1368
file_id = self.tt.final_file_id(trans_id)
1337
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)
1338
1371
self.cooked_conflicts.append(c)
1340
1373
for trans_id, conflicts in name_conflicts.iteritems():
1355
1388
if this_parent is not None and this_name is not None:
1356
1389
this_parent_path = \
1357
1390
fp.get_path(self.tt.trans_id_file_id(this_parent))
1358
this_path = pathjoin(this_parent_path, this_name)
1391
this_path = osutils.pathjoin(this_parent_path, this_name)
1360
1393
this_path = "<deleted>"
1361
1394
file_id = self.tt.final_file_id(trans_id)
1362
c = Conflict.factory('path conflict', path=this_path,
1363
conflict_path=other_path, file_id=file_id)
1395
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1396
conflict_path=other_path,
1364
1398
self.cooked_conflicts.append(c)
1365
self.cooked_conflicts.sort(key=Conflict.sort_key)
1399
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1368
1402
class WeaveMerger(Merge3Merger):
1372
1406
supports_reverse_cherrypick = False
1373
1407
history_based = True
1375
def _merged_lines(self, file_id):
1376
"""Generate the merged lines.
1377
There is no distinction between lines that are meant to contain <<<<<<<
1381
base = self.base_tree
1384
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)
1386
1423
if 'merge' in debug.debug_flags:
1387
1424
plan = list(plan)
1388
1425
trans_id = self.tt.trans_id_file_id(file_id)
1389
1426
name = self.tt.final_name(trans_id) + '.plan'
1390
contents = ('%10s|%s' % l for l in plan)
1427
contents = ('%11s|%s' % l for l in plan)
1391
1428
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1392
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1393
'>>>>>>> MERGE-SOURCE\n')
1394
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
1396
1438
def text_merge(self, file_id, trans_id):
1397
1439
"""Perform a (weave) text merge for a given file and file-id.
1398
1440
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1399
1441
and a conflict will be noted.
1401
lines, conflicts = self._merged_lines(file_id)
1443
lines, base_lines = self._merged_lines(file_id)
1402
1444
lines = list(lines)
1403
1445
# Note we're checking whether the OUTPUT is binary in this case,
1404
1446
# because we don't want to get into weave merge guts.
1405
check_text_lines(lines)
1447
textfile.check_text_lines(lines)
1406
1448
self.tt.create_file(lines, trans_id)
1449
if base_lines is not None:
1408
1451
self._raw_conflicts.append(('text conflict', trans_id))
1409
1452
name = self.tt.final_name(trans_id)
1410
1453
parent_id = self.tt.final_parent(trans_id)
1411
1454
file_group = self._dump_conflicts(name, parent_id, file_id,
1456
base_lines=base_lines)
1413
1457
file_group.append(trans_id)
1416
1460
class LCAMerger(WeaveMerger):
1418
def _merged_lines(self, file_id):
1419
"""Generate the merged lines.
1420
There is no distinction between lines that are meant to contain <<<<<<<
1424
base = self.base_tree
1427
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,
1429
if 'merge' in debug.debug_flags:
1431
trans_id = self.tt.trans_id_file_id(file_id)
1432
name = self.tt.final_name(trans_id) + '.plan'
1433
contents = ('%10s|%s' % l for l in plan)
1434
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1435
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1436
'>>>>>>> MERGE-SOURCE\n')
1437
return textmerge.merge_lines(self.reprocess)
1440
1466
class Diff3Merger(Merge3Merger):
1441
1467
"""Three-way merger using external diff3 for text merging"""
1443
1469
def dump_file(self, temp_dir, name, tree, file_id):
1444
out_path = pathjoin(temp_dir, name)
1470
out_path = osutils.pathjoin(temp_dir, name)
1445
1471
out_file = open(out_path, "wb")
1447
1473
in_file = tree.get_file(file_id)
1459
1485
import bzrlib.patch
1460
1486
temp_dir = osutils.mkdtemp(prefix="bzr-")
1462
new_file = pathjoin(temp_dir, "new")
1488
new_file = osutils.pathjoin(temp_dir, "new")
1463
1489
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1464
1490
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1465
1491
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1466
1492
status = bzrlib.patch.diff3(new_file, this, base, other)
1467
1493
if status not in (0, 1):
1468
raise BzrError("Unhandled diff3 exit code")
1494
raise errors.BzrError("Unhandled diff3 exit code")
1469
1495
f = open(new_file, 'rb')
1471
1497
self.tt.create_file(f, trans_id)