1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from itertools import chain
22
23
from bzrlib import (
42
43
WorkingTreeNotRevision,
46
from bzrlib.graph import Graph
45
47
from bzrlib.merge3 import Merge3
46
48
from bzrlib.osutils import rename, pathjoin
47
49
from progress import DummyProgress, ProgressPhase
257
259
def compare_basis(self):
259
261
basis_tree = self.revision_tree(self.this_tree.last_revision())
260
except errors.RevisionNotPresent:
262
except errors.NoSuchRevision:
261
263
basis_tree = self.this_tree.basis_tree()
262
264
changes = self.this_tree.changes_from(basis_tree)
263
265
if not changes.has_changed():
1241
1243
class _PlanMergeBase(object):
1243
def __init__(self, a_rev, b_rev, vf):
1245
def __init__(self, a_rev, b_rev, vf, key_prefix):
1246
1248
:param a_rev: Revision-id of one revision to merge
1247
1249
:param b_rev: Revision-id of the other revision to merge
1248
:param vf: A versionedfile containing both revisions
1250
:param vf: A VersionedFiles containing both revisions
1251
:param key_prefix: A prefix for accessing keys in vf, typically
1250
1254
self.a_rev = a_rev
1251
1255
self.b_rev = b_rev
1252
self.lines_a = vf.get_lines(a_rev)
1253
self.lines_b = vf.get_lines(b_rev)
1255
1257
self._last_lines = None
1256
1258
self._last_lines_revision_id = None
1257
1259
self._cached_matching_blocks = {}
1260
self._key_prefix = key_prefix
1261
lines = self.get_lines([a_rev, b_rev])
1262
self.lines_a = lines[a_rev]
1263
self.lines_b = lines[b_rev]
1265
def get_lines(self, revisions):
1266
"""Get lines for revisions from the backing VersionedFiles.
1268
:raises RevisionNotPresent: on absent texts.
1270
keys = [(self._key_prefix + (rev,)) for rev in revisions]
1272
for record in self.vf.get_record_stream(keys, 'unordered', True):
1273
if record.storage_kind == 'absent':
1274
raise errors.RevisionNotPresent(record.key, self.vf)
1275
result[record.key[-1]] = osutils.split_lines(
1276
record.get_bytes_as('fulltext'))
1259
1279
def plan_merge(self):
1260
1280
"""Generate a 'plan' for merging the two revisions.
1309
1329
if self._last_lines_revision_id == left_revision:
1310
1330
left_lines = self._last_lines
1331
right_lines = self.get_lines([right_revision])[right_revision]
1312
left_lines = self.vf.get_lines(left_revision)
1313
right_lines = self.vf.get_lines(right_revision)
1333
lines = self.get_lines([left_revision, right_revision])
1334
left_lines = lines[left_revision]
1335
right_lines = lines[right_revision]
1314
1336
self._last_lines = right_lines
1315
1337
self._last_lines_revision_id = right_revision
1316
1338
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1369
1391
class _PlanMerge(_PlanMergeBase):
1370
1392
"""Plan an annotate merge using on-the-fly annotation"""
1372
def __init__(self, a_rev, b_rev, vf):
1373
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1374
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1375
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1376
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1394
def __init__(self, a_rev, b_rev, vf, key_prefix):
1395
_PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
1397
# XXX: There is probably a better API to use to examine less history.
1398
a_ancestry = set(chain(*graph._make_breadth_first_searcher(
1399
[key_prefix + (a_rev,)])))
1400
b_ancestry = set(chain(*graph._make_breadth_first_searcher(
1401
[key_prefix + (b_rev,)])))
1402
self.uncommon = set(key[-1] for key in
1403
a_ancestry.symmetric_difference(b_ancestry))
1378
1405
def _determine_status(self, revision_id, unique_line_numbers):
1379
1406
"""Determines the status unique lines versus all lcas.
1400
1427
if version_id not in self.uncommon:
1402
parents = self.vf.get_parent_map([version_id])[version_id]
1429
key = self._key_prefix + (version_id,)
1430
parent_map = self.vf.get_parent_map([key])
1431
parents = tuple(parent[-1] for parent in parent_map[key])
1403
1432
if len(parents) == 0:
1404
return set(range(len(self.vf.get_lines(version_id))))
1433
return set(range(len(self.get_lines([version_id])[version_id])))
1406
1435
for parent in parents:
1407
1436
blocks = self._get_matching_blocks(version_id, parent)
1429
1458
This is faster, and hopefully produces more useful output.
1432
def __init__(self, a_rev, b_rev, vf, graph):
1433
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1434
self.lcas = graph.find_lca(a_rev, b_rev)
1461
def __init__(self, a_rev, b_rev, vf, key_prefix, graph):
1462
_PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
1463
lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1466
if lca == NULL_REVISION:
1469
self.lcas.add(lca[-1])
1435
1470
for lca in self.lcas:
1436
1471
if _mod_revision.is_null(lca):
1439
lca_lines = self.vf.get_lines(lca)
1474
lca_lines = self.get_lines([lca])[lca]
1440
1475
matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
1442
1477
blocks = list(matcher.get_matching_blocks())