13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from itertools import chain
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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):
72
54
def __init__(self, this_branch, other_tree=None, base_tree=None,
73
this_tree=None, pb=DummyProgress(), change_reporter=None,
55
this_tree=None, pb=None, change_reporter=None,
74
56
recurse='down', revision_graph=None):
75
57
object.__init__(self)
76
58
self.this_branch = this_branch
225
220
if revno is None:
226
221
tree = workingtree.WorkingTree.open_containing(location)[0]
227
222
return tree.branch, tree
228
branch = Branch.open_containing(location, possible_transports)[0]
223
branch = _mod_branch.Branch.open_containing(
224
location, possible_transports)[0]
230
226
revision_id = branch.last_revision()
232
228
revision_id = branch.get_rev_id(revno)
233
revision_id = ensure_null(revision_id)
229
revision_id = _mod_revision.ensure_null(revision_id)
234
230
return branch, self.revision_tree(revision_id, branch)
232
@deprecated_method(deprecated_in((2, 1, 0)))
236
233
def ensure_revision_trees(self):
237
234
if self.this_revision_tree is None:
238
235
self.this_basis_tree = self.revision_tree(self.this_basis)
242
239
if self.other_rev_id is None:
243
240
other_basis_tree = self.revision_tree(self.other_basis)
244
changes = other_basis_tree.changes_from(self.other_tree)
245
if changes.has_changed():
246
raise WorkingTreeNotRevision(self.this_tree)
241
if other_basis_tree.has_changes(self.other_tree):
242
raise errors.WorkingTreeNotRevision(self.this_tree)
247
243
other_rev_id = self.other_basis
248
244
self.other_tree = other_basis_tree
246
@deprecated_method(deprecated_in((2, 1, 0)))
250
247
def file_revisions(self, file_id):
251
248
self.ensure_revision_trees()
252
249
def get_id(tree, file_id):
255
252
if self.this_rev_id is None:
256
253
if self.this_basis_tree.get_file_sha1(file_id) != \
257
254
self.this_tree.get_file_sha1(file_id):
258
raise WorkingTreeNotRevision(self.this_tree)
255
raise errors.WorkingTreeNotRevision(self.this_tree)
260
257
trees = (self.this_basis_tree, self.other_tree)
261
258
return [get_id(tree, file_id) for tree in trees]
260
@deprecated_method(deprecated_in((2, 1, 0)))
263
261
def check_basis(self, check_clean, require_commits=True):
264
262
if self.this_basis is None and require_commits is True:
265
raise BzrCommandError("This branch has no commits."
266
" (perhaps you would prefer 'bzr pull')")
263
raise errors.BzrCommandError(
264
"This branch has no commits."
265
" (perhaps you would prefer 'bzr pull')")
268
267
self.compare_basis()
269
268
if self.this_basis != self.this_rev_id:
270
269
raise errors.UncommittedChanges(self.this_tree)
271
@deprecated_method(deprecated_in((2, 1, 0)))
272
272
def compare_basis(self):
274
274
basis_tree = self.revision_tree(self.this_tree.last_revision())
275
275
except errors.NoSuchRevision:
276
276
basis_tree = self.this_tree.basis_tree()
277
changes = self.this_tree.changes_from(basis_tree)
278
if not changes.has_changed():
277
if not self.this_tree.has_changes(basis_tree):
279
278
self.this_rev_id = self.this_basis
281
280
def set_interesting_files(self, file_list):
282
281
self.interesting_files = file_list
284
283
def set_pending(self):
285
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):
287
287
self._add_parent()
360
360
target.fetch(source, revision_id)
362
362
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
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
367
367
self.base_tree = self.revision_tree(self.base_rev_id)
368
368
self._is_criss_cross = False
370
370
lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
371
371
self._is_criss_cross = False
372
372
if len(lcas) == 0:
373
self.base_rev_id = NULL_REVISION
373
self.base_rev_id = _mod_revision.NULL_REVISION
374
374
elif len(lcas) == 1:
375
375
self.base_rev_id = list(lcas)[0]
376
376
else: # len(lcas) > 1
439
439
if self.merge_type.supports_reprocess:
440
440
kwargs['reprocess'] = self.reprocess
441
441
elif self.reprocess:
442
raise BzrError("Conflict reduction is not supported for merge"
443
" type %s." % self.merge_type)
442
raise errors.BzrError(
443
"Conflict reduction is not supported for merge"
444
" type %s." % self.merge_type)
444
445
if self.merge_type.supports_show_base:
445
446
kwargs['show_base'] = self.show_base
446
447
elif self.show_base:
447
raise BzrError("Showing base is not supported for this"
448
" merge type. %s" % self.merge_type)
448
raise errors.BzrError("Showing base is not supported for this"
449
" merge type. %s" % self.merge_type)
449
450
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
450
451
and not self.base_is_other_ancestor):
451
452
raise errors.CannotReverseCherrypick()
534
538
winner_idx = {"this": 2, "other": 1, "conflict": 1}
535
539
supports_lca_trees = True
537
def __init__(self, working_tree, this_tree, base_tree, other_tree,
541
def __init__(self, working_tree, this_tree, base_tree, other_tree,
538
542
interesting_ids=None, reprocess=False, show_base=False,
539
pb=DummyProgress(), pp=None, change_reporter=None,
543
pb=progress.DummyProgress(), pp=None, change_reporter=None,
540
544
interesting_files=None, do_merge=True,
541
545
cherrypick=False, lca_trees=None):
542
546
"""Initialize the merger object and perform the merge.
596
600
self.this_tree.lock_tree_write()
597
601
self.base_tree.lock_read()
598
602
self.other_tree.lock_read()
599
self.tt = TreeTransform(self.this_tree, self.pb)
602
self._compute_transform()
604
results = self.tt.apply(no_conflicts=True)
605
self.write_modified(results)
604
self.tt = transform.TreeTransform(self.this_tree, self.pb)
607
self.this_tree.add_conflicts(self.cooked_conflicts)
608
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:
612
618
self.other_tree.unlock()
613
619
self.base_tree.unlock()
614
620
self.this_tree.unlock()
1070
1076
if name_winner == "conflict":
1071
1077
trans_id = self.tt.trans_id_file_id(file_id)
1072
self._raw_conflicts.append(('name conflict', trans_id,
1078
self._raw_conflicts.append(('name conflict', trans_id,
1073
1079
this_name, other_name))
1074
1080
if parent_id_winner == "conflict":
1075
1081
trans_id = self.tt.trans_id_file_id(file_id)
1076
self._raw_conflicts.append(('parent conflict', trans_id,
1082
self._raw_conflicts.append(('parent conflict', trans_id,
1077
1083
this_parent, other_parent))
1078
1084
if other_name is None:
1079
# it doesn't matter whether the result was 'other' or
1085
# it doesn't matter whether the result was 'other' or
1080
1086
# 'conflict'-- if there's no 'other', we leave it alone.
1082
1088
# if we get here, name_winner and parent_winner are set to safe values.
1143
1149
self.tt.delete_contents(trans_id)
1144
1150
if file_id in self.other_tree:
1145
1151
# OTHER changed the file
1146
create_from_tree(self.tt, trans_id,
1147
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)
1148
1168
if not file_in_this:
1149
1169
self.tt.version_file(file_id, trans_id)
1150
1170
return "modified"
1220
1240
self._raw_conflicts.append(('text conflict', trans_id))
1221
1241
name = self.tt.final_name(trans_id)
1222
1242
parent_id = self.tt.final_parent(trans_id)
1223
file_group = self._dump_conflicts(name, parent_id, file_id,
1243
file_group = self._dump_conflicts(name, parent_id, file_id,
1224
1244
this_lines, base_lines,
1226
1246
file_group.append(trans_id)
1228
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1248
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1229
1249
base_lines=None, other_lines=None, set_version=False,
1230
1250
no_base=False):
1231
1251
"""Emit conflict files.
1233
1253
determined automatically. If set_version is true, the .OTHER, .THIS
1234
1254
or .BASE (in that order) will be created as versioned files.
1236
data = [('OTHER', self.other_tree, other_lines),
1256
data = [('OTHER', self.other_tree, other_lines),
1237
1257
('THIS', self.this_tree, this_lines)]
1238
1258
if not no_base:
1239
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
1240
1274
versioned = False
1241
1275
file_group = []
1242
1276
for suffix, tree, lines in data:
1243
1277
if file_id in tree:
1244
1278
trans_id = self._conflict_file(name, parent_id, tree, file_id,
1279
suffix, lines, filter_tree_path)
1246
1280
file_group.append(trans_id)
1247
1281
if set_version and not versioned:
1248
1282
self.tt.version_file(file_id, trans_id)
1249
1283
versioned = True
1250
1284
return file_group
1252
1286
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1287
lines=None, filter_tree_path=None):
1254
1288
"""Emit a single conflict file."""
1255
1289
name = name + '.' + suffix
1256
1290
trans_id = self.tt.create_path(name, parent_id)
1257
create_from_tree(self.tt, trans_id, tree, file_id, lines)
1291
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
1258
1293
return trans_id
1260
1295
def merge_executable(self, file_id, file_status):
1324
1359
if path.endswith(suffix):
1325
1360
path = path[:-len(suffix)]
1327
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)
1328
1364
self.cooked_conflicts.append(c)
1329
1365
if conflict_type == 'text conflict':
1330
1366
trans_id = conflict[1]
1331
1367
path = fp.get_path(trans_id)
1332
1368
file_id = self.tt.final_file_id(trans_id)
1333
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)
1334
1371
self.cooked_conflicts.append(c)
1336
1373
for trans_id, conflicts in name_conflicts.iteritems():
1351
1388
if this_parent is not None and this_name is not None:
1352
1389
this_parent_path = \
1353
1390
fp.get_path(self.tt.trans_id_file_id(this_parent))
1354
this_path = pathjoin(this_parent_path, this_name)
1391
this_path = osutils.pathjoin(this_parent_path, this_name)
1356
1393
this_path = "<deleted>"
1357
1394
file_id = self.tt.final_file_id(trans_id)
1358
c = Conflict.factory('path conflict', path=this_path,
1359
conflict_path=other_path, file_id=file_id)
1395
c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1396
conflict_path=other_path,
1360
1398
self.cooked_conflicts.append(c)
1361
self.cooked_conflicts.sort(key=Conflict.sort_key)
1399
self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1364
1402
class WeaveMerger(Merge3Merger):
1368
1406
supports_reverse_cherrypick = False
1369
1407
history_based = True
1371
def _merged_lines(self, file_id):
1372
"""Generate the merged lines.
1373
There is no distinction between lines that are meant to contain <<<<<<<
1377
base = self.base_tree
1380
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)
1382
1423
if 'merge' in debug.debug_flags:
1383
1424
plan = list(plan)
1384
1425
trans_id = self.tt.trans_id_file_id(file_id)
1385
1426
name = self.tt.final_name(trans_id) + '.plan'
1386
contents = ('%10s|%s' % l for l in plan)
1427
contents = ('%11s|%s' % l for l in plan)
1387
1428
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1388
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1389
'>>>>>>> MERGE-SOURCE\n')
1390
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
1392
1438
def text_merge(self, file_id, trans_id):
1393
1439
"""Perform a (weave) text merge for a given file and file-id.
1394
1440
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1395
1441
and a conflict will be noted.
1397
lines, conflicts = self._merged_lines(file_id)
1443
lines, base_lines = self._merged_lines(file_id)
1398
1444
lines = list(lines)
1399
# Note we're checking whether the OUTPUT is binary in this case,
1445
# Note we're checking whether the OUTPUT is binary in this case,
1400
1446
# because we don't want to get into weave merge guts.
1401
check_text_lines(lines)
1447
textfile.check_text_lines(lines)
1402
1448
self.tt.create_file(lines, trans_id)
1449
if base_lines is not None:
1404
1451
self._raw_conflicts.append(('text conflict', trans_id))
1405
1452
name = self.tt.final_name(trans_id)
1406
1453
parent_id = self.tt.final_parent(trans_id)
1407
file_group = self._dump_conflicts(name, parent_id, file_id,
1454
file_group = self._dump_conflicts(name, parent_id, file_id,
1456
base_lines=base_lines)
1409
1457
file_group.append(trans_id)
1412
1460
class LCAMerger(WeaveMerger):
1414
def _merged_lines(self, file_id):
1415
"""Generate the merged lines.
1416
There is no distinction between lines that are meant to contain <<<<<<<
1420
base = self.base_tree
1423
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,
1425
if 'merge' in debug.debug_flags:
1427
trans_id = self.tt.trans_id_file_id(file_id)
1428
name = self.tt.final_name(trans_id) + '.plan'
1429
contents = ('%10s|%s' % l for l in plan)
1430
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1431
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1432
'>>>>>>> MERGE-SOURCE\n')
1433
return textmerge.merge_lines(self.reprocess)
1436
1466
class Diff3Merger(Merge3Merger):
1437
1467
"""Three-way merger using external diff3 for text merging"""
1439
1469
def dump_file(self, temp_dir, name, tree, file_id):
1440
out_path = pathjoin(temp_dir, name)
1470
out_path = osutils.pathjoin(temp_dir, name)
1441
1471
out_file = open(out_path, "wb")
1443
1473
in_file = tree.get_file(file_id)
1455
1485
import bzrlib.patch
1456
1486
temp_dir = osutils.mkdtemp(prefix="bzr-")
1458
new_file = pathjoin(temp_dir, "new")
1488
new_file = osutils.pathjoin(temp_dir, "new")
1459
1489
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1460
1490
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1461
1491
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1462
1492
status = bzrlib.patch.diff3(new_file, this, base, other)
1463
1493
if status not in (0, 1):
1464
raise BzrError("Unhandled diff3 exit code")
1494
raise errors.BzrError("Unhandled diff3 exit code")
1465
1495
f = open(new_file, 'rb')
1467
1497
self.tt.create_file(f, trans_id)
1485
1515
other_rev_id=None,
1486
1516
interesting_files=None,
1487
1517
this_tree=None,
1518
pb=progress.DummyProgress(),
1489
1519
change_reporter=None):
1490
"""Primary interface for merging.
1520
"""Primary interface for merging.
1492
typical use is probably
1522
typical use is probably
1493
1523
'merge_inner(branch, branch.get_revision_tree(other_revision),
1494
1524
branch.get_revision_tree(base_revision))'
1496
1526
if this_tree is None:
1497
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1498
"parameter as of bzrlib version 0.8.")
1527
raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1528
"parameter as of bzrlib version 0.8.")
1499
1529
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1500
1530
pb=pb, change_reporter=change_reporter)
1501
1531
merger.backup_files = backup_files