~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1139
1139
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1140
1140
            assert text_a == text_b
1141
1141
            yield "unchanged", text_a
 
1142
 
 
1143
 
 
1144
class _PlanMerge(object):
 
1145
    """Plan an annotate merge using on-the-fly annotation"""
 
1146
 
 
1147
    def __init__(self, a_rev, b_rev, vf):
 
1148
        """Contructor.
 
1149
 
 
1150
        :param a_rev: Revision-id of one revision to merge
 
1151
        :param b_rev: Revision-id of the other revision to merge
 
1152
        :param vf: A versionedfile containing both revisions
 
1153
        """
 
1154
        self.a_rev = a_rev
 
1155
        self.b_rev = b_rev
 
1156
        self.lines_a = vf.get_lines(a_rev)
 
1157
        self.lines_b = vf.get_lines(b_rev)
 
1158
        self.vf = vf
 
1159
        a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
 
1160
        b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
 
1161
        self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
 
1162
        self._last_lines = None
 
1163
        self._last_lines_revision_id = None
 
1164
 
 
1165
    def plan_merge(self):
 
1166
        """Generate a 'plan' for merging the two revisions.
 
1167
 
 
1168
        This involves comparing their texts and determining the cause of
 
1169
        differences.  If text A has a line and text B does not, then either the
 
1170
        line was added to text A, or it was deleted from B.  Once the causes
 
1171
        are combined, they are written out in the format described in
 
1172
        VersionedFile.plan_merge
 
1173
        """
 
1174
        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
 
1175
        new_a = self._find_new(self.a_rev)
 
1176
        new_b = self._find_new(self.b_rev)
 
1177
        last_i = 0
 
1178
        last_j = 0
 
1179
        a_lines = self.vf.get_lines(self.a_rev)
 
1180
        b_lines = self.vf.get_lines(self.b_rev)
 
1181
        for i, j, n in blocks:
 
1182
            # determine why lines aren't common
 
1183
            for a_index in range(last_i, i):
 
1184
                if a_index in new_a:
 
1185
                    cause = 'new-a'
 
1186
                else:
 
1187
                    cause = 'killed-b'
 
1188
                yield cause, a_lines[a_index]
 
1189
            for b_index in range(last_j, j):
 
1190
                if b_index in new_b:
 
1191
                    cause = 'new-b'
 
1192
                else:
 
1193
                    cause = 'killed-a'
 
1194
                yield cause, b_lines[b_index]
 
1195
            # handle common lines
 
1196
            for a_index in range(i, i+n):
 
1197
                yield 'unchanged', a_lines[a_index]
 
1198
            last_i = i+n
 
1199
            last_j = j+n
 
1200
 
 
1201
    def _get_matching_blocks(self, left_revision, right_revision):
 
1202
        """Return a description of which sections of two revisions match.
 
1203
 
 
1204
        See SequenceMatcher.get_matching_blocks
 
1205
        """
 
1206
        if self._last_lines_revision_id == left_revision:
 
1207
            left_lines = self._last_lines
 
1208
        else:
 
1209
            left_lines = self.vf.get_lines(left_revision)
 
1210
        right_lines = self.vf.get_lines(right_revision)
 
1211
        self._last_lines = right_lines
 
1212
        self._last_lines_revision_id = right_revision
 
1213
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
 
1214
                                                       right_lines)
 
1215
        return matcher.get_matching_blocks()
 
1216
 
 
1217
    def _unique_lines(self, matching_blocks):
 
1218
        """Analyse matching_blocks to determine which lines are unique
 
1219
 
 
1220
        :return: a tuple of (unique_left, unique_right), where the values are
 
1221
            sets of line numbers of unique lines.
 
1222
        """
 
1223
        last_i = 0
 
1224
        last_j = 0
 
1225
        unique_left = []
 
1226
        unique_right = []
 
1227
        for i, j, n in matching_blocks:
 
1228
            unique_left.extend(range(last_i, i))
 
1229
            unique_right.extend(range(last_j, j))
 
1230
            last_i = i + n
 
1231
            last_j = j + n
 
1232
        return unique_left, unique_right
 
1233
 
 
1234
    def _find_new(self, version_id):
 
1235
        """Determine which lines are new in the ancestry of this version.
 
1236
 
 
1237
        If a lines is present in this version, and not present in any
 
1238
        common ancestor, it is considered new.
 
1239
        """
 
1240
        if version_id not in self.uncommon:
 
1241
            return set()
 
1242
        parents = self.vf.get_parents(version_id)
 
1243
        if len(parents) == 0:
 
1244
            return set(range(len(self.vf.get_lines(version_id))))
 
1245
        new = None
 
1246
        for parent in parents:
 
1247
            blocks = self._get_matching_blocks(version_id, parent)
 
1248
            result, unused = self._unique_lines(blocks)
 
1249
            parent_new = self._find_new(parent)
 
1250
            for i, j, n in blocks:
 
1251
                for ii, jj in [(i+r, j+r) for r in range(n)]:
 
1252
                    if jj in parent_new:
 
1253
                        result.append(ii)
 
1254
            if new is None:
 
1255
                new = set(result)
 
1256
            else:
 
1257
                new.intersection_update(result)
 
1258
        return new