/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/revision.py

- properties are retrieved when revisions are loaded

- test for this

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# (C) 2005 Canonical
 
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
 
 
18
import bzrlib.errors
 
19
from bzrlib.graph import node_distances, select_farthest, all_descendants
 
20
 
 
21
NULL_REVISION="null:"
 
22
 
 
23
class Revision(object):
 
24
    """Single revision on a branch.
 
25
 
 
26
    Revisions may know their revision_hash, but only once they've been
 
27
    written out.  This is not stored because you cannot write the hash
 
28
    into the file it describes.
 
29
 
 
30
    After bzr 0.0.5 revisions are allowed to have multiple parents.
 
31
 
 
32
    parent_ids
 
33
        List of parent revision_ids
 
34
    """
 
35
    
 
36
    def __init__(self, revision_id, properties=None, **args):
 
37
        self.revision_id = revision_id
 
38
        self.properties = properties or {}
 
39
        self.__dict__.update(args)
 
40
        self.parent_ids = []
 
41
        self.parent_sha1s = []
 
42
 
 
43
    def __repr__(self):
 
44
        return "<Revision id %s>" % self.revision_id
 
45
 
 
46
    def __eq__(self, other):
 
47
        if not isinstance(other, Revision):
 
48
            return False
 
49
        # FIXME: rbc 20050930 parent_ids are not being compared
 
50
        return (
 
51
                self.inventory_sha1 == other.inventory_sha1
 
52
                and self.revision_id == other.revision_id
 
53
                and self.timestamp == other.timestamp
 
54
                and self.message == other.message
 
55
                and self.timezone == other.timezone
 
56
                and self.committer == other.committer
 
57
                and self.properties == other.properties)
 
58
 
 
59
    def __ne__(self, other):
 
60
        return not self.__eq__(other)
 
61
 
 
62
 
 
63
def is_ancestor(revision_id, candidate_id, branch):
 
64
    """Return true if candidate_id is an ancestor of revision_id.
 
65
 
 
66
    A false negative will be returned if any intermediate descendent of
 
67
    candidate_id is not present in any of the revision_sources.
 
68
    
 
69
    revisions_source is an object supporting a get_revision operation that
 
70
    behaves like Branch's.
 
71
    """
 
72
    return candidate_id in branch.get_ancestry(revision_id)
 
73
 
 
74
 
 
75
def iter_ancestors(revision_id, revision_source, only_present=False):
 
76
    ancestors = (revision_id,)
 
77
    distance = 0
 
78
    while len(ancestors) > 0:
 
79
        new_ancestors = []
 
80
        for ancestor in ancestors:
 
81
            if not only_present:
 
82
                yield ancestor, distance
 
83
            try:
 
84
                revision = revision_source.get_revision(ancestor)
 
85
            except bzrlib.errors.NoSuchRevision, e:
 
86
                if e.revision == revision_id:
 
87
                    raise 
 
88
                else:
 
89
                    continue
 
90
            if only_present:
 
91
                yield ancestor, distance
 
92
            new_ancestors.extend(revision.parent_ids)
 
93
        ancestors = new_ancestors
 
94
        distance += 1
 
95
 
 
96
 
 
97
def find_present_ancestors(revision_id, revision_source):
 
98
    """Return the ancestors of a revision present in a branch.
 
99
 
 
100
    It's possible that a branch won't have the complete ancestry of
 
101
    one of its revisions.  
 
102
 
 
103
    """
 
104
    found_ancestors = {}
 
105
    anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
 
106
                         only_present=True))
 
107
    for anc_order, (anc_id, anc_distance) in anc_iter:
 
108
        if not found_ancestors.has_key(anc_id):
 
109
            found_ancestors[anc_id] = (anc_order, anc_distance)
 
110
    return found_ancestors
 
111
    
 
112
 
 
113
def __get_closest(intersection):
 
114
    intersection.sort()
 
115
    matches = [] 
 
116
    for entry in intersection:
 
117
        if entry[0] == intersection[0][0]:
 
118
            matches.append(entry[2])
 
119
    return matches
 
120
 
 
121
 
 
122
def old_common_ancestor(revision_a, revision_b, revision_source):
 
123
    """Find the ancestor common to both revisions that is closest to both.
 
124
    """
 
125
    from bzrlib.trace import mutter
 
126
    a_ancestors = find_present_ancestors(revision_a, revision_source)
 
127
    b_ancestors = find_present_ancestors(revision_b, revision_source)
 
128
    a_intersection = []
 
129
    b_intersection = []
 
130
    # a_order is used as a tie-breaker when two equally-good bases are found
 
131
    for revision, (a_order, a_distance) in a_ancestors.iteritems():
 
132
        if b_ancestors.has_key(revision):
 
133
            a_intersection.append((a_distance, a_order, revision))
 
134
            b_intersection.append((b_ancestors[revision][1], a_order, revision))
 
135
    mutter("a intersection: %r" % a_intersection)
 
136
    mutter("b intersection: %r" % b_intersection)
 
137
 
 
138
    a_closest = __get_closest(a_intersection)
 
139
    if len(a_closest) == 0:
 
140
        return None
 
141
    b_closest = __get_closest(b_intersection)
 
142
    assert len(b_closest) != 0
 
143
    mutter ("a_closest %r" % a_closest)
 
144
    mutter ("b_closest %r" % b_closest)
 
145
    if a_closest[0] in b_closest:
 
146
        return a_closest[0]
 
147
    elif b_closest[0] in a_closest:
 
148
        return b_closest[0]
 
149
    else:
 
150
        raise bzrlib.errors.AmbiguousBase((a_closest[0], b_closest[0]))
 
151
    return a_closest[0]
 
152
 
 
153
def revision_graph(revision, revision_source):
 
154
    """Produce a graph of the ancestry of the specified revision.
 
155
    Return root, ancestors map, descendants map
 
156
 
 
157
    TODO: Produce graphs with the NULL revision as root, so that we can find
 
158
    a common even when trees are not branches don't represent a single line
 
159
    of descent.
 
160
    """
 
161
    ancestors = {}
 
162
    descendants = {}
 
163
    lines = [revision]
 
164
    root = None
 
165
    descendants[revision] = {}
 
166
    while len(lines) > 0:
 
167
        new_lines = set()
 
168
        for line in lines:
 
169
            if line == NULL_REVISION:
 
170
                parents = []
 
171
                root = NULL_REVISION
 
172
            else:
 
173
                try:
 
174
                    rev = revision_source.get_revision(line)
 
175
                    parents = list(rev.parent_ids)
 
176
                    if len(parents) == 0:
 
177
                        parents = [NULL_REVISION]
 
178
                except bzrlib.errors.NoSuchRevision:
 
179
                    if line == revision:
 
180
                        raise
 
181
                    parents = None
 
182
            if parents is not None:
 
183
                for parent in parents:
 
184
                    if parent not in ancestors:
 
185
                        new_lines.add(parent)
 
186
                    if parent not in descendants:
 
187
                        descendants[parent] = {}
 
188
                    descendants[parent][line] = 1
 
189
            if parents is not None:
 
190
                ancestors[line] = set(parents)
 
191
        lines = new_lines
 
192
    assert root not in descendants[root]
 
193
    assert root not in ancestors[root]
 
194
    return root, ancestors, descendants
 
195
 
 
196
 
 
197
def combined_graph(revision_a, revision_b, revision_source):
 
198
    """Produce a combined ancestry graph.
 
199
    Return graph root, ancestors map, descendants map, set of common nodes"""
 
200
    root, ancestors, descendants = revision_graph(revision_a, revision_source)
 
201
    root_b, ancestors_b, descendants_b = revision_graph(revision_b, 
 
202
                                                        revision_source)
 
203
    if root != root_b:
 
204
        raise bzrlib.errors.NoCommonRoot(revision_a, revision_b)
 
205
    common = set()
 
206
    for node, node_anc in ancestors_b.iteritems():
 
207
        if node in ancestors:
 
208
            common.add(node)
 
209
        else:
 
210
            ancestors[node] = set()
 
211
        ancestors[node].update(node_anc)
 
212
    for node, node_dec in descendants_b.iteritems():
 
213
        if node not in descendants:
 
214
            descendants[node] = {}
 
215
        descendants[node].update(node_dec)
 
216
    return root, ancestors, descendants, common
 
217
 
 
218
 
 
219
def common_ancestor(revision_a, revision_b, revision_source):
 
220
    try:
 
221
        root, ancestors, descendants, common = \
 
222
            combined_graph(revision_a, revision_b, revision_source)
 
223
    except bzrlib.errors.NoCommonRoot:
 
224
        raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
 
225
        
 
226
    distances = node_distances (descendants, ancestors, root)
 
227
    farthest = select_farthest(distances, common)
 
228
    if farthest is None or farthest == NULL_REVISION:
 
229
        raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
 
230
    return farthest
 
231
 
 
232
 
 
233
class MultipleRevisionSources(object):
 
234
    """Proxy that looks in multiple branches for revisions."""
 
235
    def __init__(self, *args):
 
236
        object.__init__(self)
 
237
        assert len(args) != 0
 
238
        self._revision_sources = args
 
239
 
 
240
    def get_revision(self, revision_id):
 
241
        for source in self._revision_sources:
 
242
            try:
 
243
                return source.get_revision(revision_id)
 
244
            except bzrlib.errors.NoSuchRevision, e:
 
245
                pass
 
246
        raise e
 
247
 
 
248
def get_intervening_revisions(ancestor_id, rev_id, rev_source, 
 
249
                              revision_history=None):
 
250
    """Find the longest line of descent from maybe_ancestor to revision.
 
251
    Revision history is followed where possible.
 
252
 
 
253
    If ancestor_id == rev_id, list will be empty.
 
254
    Otherwise, rev_id will be the last entry.  ancestor_id will never appear.
 
255
    If ancestor_id is not an ancestor, NotAncestor will be thrown
 
256
    """
 
257
    root, ancestors, descendants = revision_graph(rev_id, rev_source)
 
258
    if len(descendants) == 0:
 
259
        raise NoSuchRevision(rev_source, rev_id)
 
260
    if ancestor_id not in descendants:
 
261
        rev_source.get_revision(ancestor_id)
 
262
        raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
 
263
    root_descendants = all_descendants(descendants, ancestor_id)
 
264
    root_descendants.add(ancestor_id)
 
265
    if rev_id not in root_descendants:
 
266
        raise bzrlib.errors.NotAncestor(rev_id, ancestor_id)
 
267
    distances = node_distances(descendants, ancestors, ancestor_id,
 
268
                               root_descendants=root_descendants)
 
269
 
 
270
    def best_ancestor(rev_id):
 
271
        best = None
 
272
        for anc_id in ancestors[rev_id]:
 
273
            try:
 
274
                distance = distances[anc_id]
 
275
            except KeyError:
 
276
                continue
 
277
            if revision_history is not None and anc_id in revision_history:
 
278
                return anc_id
 
279
            elif best is None or distance > best[1]:
 
280
                best = (anc_id, distance)
 
281
        return best[0]
 
282
 
 
283
    next = rev_id
 
284
    path = []
 
285
    while next != ancestor_id:
 
286
        path.append(next)
 
287
        next = best_ancestor(next)
 
288
    path.reverse()
 
289
    return path