/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/plan_merge.py

  • Committer: Aaron Bentley
  • Date: 2007-12-02 18:03:56 UTC
  • mto: This revision was merged to the branch mainline in revision 3073.
  • Revision ID: aaron.bentley@utoronto.ca-20071202180356-fzo74355tuzq3r6m
Update NEWS entry

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
from bzrlib import errors, patiencediff, revision
 
18
 
 
19
class PlanMerge(object):
 
20
    """Plan an annotate merge using on-the-fly annotation"""
 
21
 
 
22
    def __init__(self, a_rev, b_rev, vf):
 
23
        self.a_rev = a_rev
 
24
        self.b_rev = b_rev
 
25
        self.lines_a = vf.get_lines(a_rev)
 
26
        self.lines_b = vf.get_lines(b_rev)
 
27
        self.vf = vf
 
28
        a_ancestry = set(vf.get_ancestry(a_rev))
 
29
        b_ancestry = set(vf.get_ancestry(b_rev))
 
30
        self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
 
31
 
 
32
    def plan_merge(self):
 
33
        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
 
34
        new_a = self._find_new(self.a_rev)
 
35
        new_b = self._find_new(self.b_rev)
 
36
        last_i = 0
 
37
        last_j = 0
 
38
        a_lines = self.vf.get_lines(self.a_rev)
 
39
        b_lines = self.vf.get_lines(self.b_rev)
 
40
        for i, j, n in blocks:
 
41
            # determine why lines aren't common
 
42
            for a_index in range(last_i, i):
 
43
                if a_index in new_a:
 
44
                    cause = 'new-a'
 
45
                else:
 
46
                    cause = 'killed-b'
 
47
                yield cause, a_lines[a_index]
 
48
            for b_index in range(last_j, j):
 
49
                if b_index in new_b:
 
50
                    cause = 'new-b'
 
51
                else:
 
52
                    cause = 'killed-a'
 
53
                yield cause, b_lines[b_index]
 
54
            # handle common lines
 
55
            for a_index in range(i, i+n):
 
56
                yield 'unchanged', a_lines[a_index]
 
57
            last_i = i+n
 
58
            last_j = j+n
 
59
 
 
60
    def _get_matching_blocks(self, left_revision, right_revision):
 
61
        left_lines = self.vf.get_lines(left_revision)
 
62
        right_lines = self.vf.get_lines(right_revision)
 
63
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
 
64
                                                       right_lines)
 
65
        return matcher.get_matching_blocks()
 
66
 
 
67
    def _unique_lines(self, matching_blocks):
 
68
        last_i = 0
 
69
        last_j = 0
 
70
        unique_left = []
 
71
        unique_right = []
 
72
        for i, j, n in matching_blocks:
 
73
            unique_left.extend(range(last_i, i))
 
74
            unique_right.extend(range(last_j, j))
 
75
            last_i = i + n
 
76
            last_j = j + n
 
77
        return unique_left, unique_right
 
78
 
 
79
    def _find_new(self, version_id):
 
80
        """Determine which lines are new in the ancestry of this version.
 
81
 
 
82
        If a lines is present in this version, and not present in any
 
83
        common ancestor, it is considered new.
 
84
        """
 
85
        if version_id not in self.uncommon:
 
86
            return set()
 
87
        parents = self.vf.get_parents(version_id)
 
88
        if len(parents) == 0:
 
89
            return set(range(len(self.vf.get_lines(version_id))))
 
90
        new = None
 
91
        for parent in parents:
 
92
            blocks = self._get_matching_blocks(version_id, parent)
 
93
            result, unused = self._unique_lines(blocks)
 
94
            parent_new = self._find_new(parent)
 
95
            for i, j, n in blocks:
 
96
                for ii, jj in [(i+r, j+r) for r in range(n)]:
 
97
                    if jj in parent_new:
 
98
                        result.append(ii)
 
99
            if new is None:
 
100
                new = set(result)
 
101
            else:
 
102
                new.intersection_update(result)
 
103
        return new
 
104
 
 
105
 
 
106
class _PlanMergeVersionedfile(object):
 
107
    """A VersionedFile for uncommitted and committed texts.
 
108
 
 
109
    Intended to allow merges to be planned with working tree texts.
 
110
    """
 
111
 
 
112
    def __init__(self, file_id, fallback_versionedfiles=None):
 
113
        self._file_id = file_id
 
114
        if fallback_versionedfiles is None:
 
115
            self.fallback_versionedfiles = []
 
116
        else:
 
117
            self.fallback_versionedfiles = fallback_versionedfiles
 
118
        self._parents = {}
 
119
        self._lines = {}
 
120
 
 
121
    def add_lines(self, version_id, parents, lines):
 
122
        if not revision.is_reserved_id(version_id):
 
123
            raise ValueError('Only reserved ids may be used')
 
124
        if parents is None:
 
125
            raise ValueError('Parents may not be None')
 
126
        if lines is None:
 
127
            raise ValueError('Lines may not be None')
 
128
        self._parents[version_id] = parents
 
129
        self._lines[version_id] = lines
 
130
 
 
131
    def get_lines(self, version_id):
 
132
        lines = self._lines.get(version_id)
 
133
        if lines is not None:
 
134
            return lines
 
135
        for versionedfile in self.fallback_versionedfiles:
 
136
            try:
 
137
                return versionedfile.get_lines(version_id)
 
138
            except errors.RevisionNotPresent:
 
139
                continue
 
140
        else:
 
141
            raise errors.RevisionNotPresent(version_id, self._file_id)
 
142
 
 
143
    def get_ancestry(self, version_id):
 
144
        """See Versionedfile.get_ancestry.
 
145
 
 
146
        Note that this implementation assumes that if a Versionedfile can
 
147
        answer get_ancestry at all, it can give an authoritative answer.  In
 
148
        fact, ghosts can invalidate this assumption.  But it's good enough
 
149
        99% of the time, and far cheaper/simpler.
 
150
 
 
151
        Also note that the results of this version are never topologically
 
152
        sorted, and are a set.
 
153
        """
 
154
        parents = self._parents.get(version_id)
 
155
        if parents is None:
 
156
            for vf in self.fallback_versionedfiles:
 
157
                try:
 
158
                    return vf.get_ancestry(version_id)
 
159
                except errors.RevisionNotPresent:
 
160
                    continue
 
161
            else:
 
162
                raise errors.RevisionNotPresent(version_id, self._file_id)
 
163
        ancestry = set([version_id])
 
164
        for parent in parents:
 
165
            ancestry.update(self.get_ancestry(parent))
 
166
        return ancestry
 
167
 
 
168
    def get_parents(self, version_id):
 
169
        parents = self._parents.get(version_id)
 
170
        if parents is not None:
 
171
            return parents
 
172
        for versionedfile in self.fallback_versionedfiles:
 
173
            try:
 
174
                return versionedfile.get_parents(version_id)
 
175
            except errors.RevisionNotPresent:
 
176
                continue
 
177
        else:
 
178
            raise errors.RevisionNotPresent(version_id, self._file_id)