~bzr-pqm/bzr/bzr.dev

1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
1
# Copyright (C) 2005, 2006 Canonical
1 by mbp at sourcefrog
import from baz patch-364
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
1185.16.40 by Martin Pool
todo
17
# TODO: Some kind of command-line display of revision properties: 
18
# perhaps show them in log -v and allow them as options to the commit command.
1 by mbp at sourcefrog
import from baz patch-364
19
1590.1.1 by Robert Collins
Improve common_ancestor performance.
20
21
import bzrlib.errors as errors
1594.2.15 by Robert Collins
Unfuck performance.
22
from bzrlib.graph import node_distances, select_farthest, all_descendants, Graph
1185.16.39 by Martin Pool
- constraints on revprops
23
from bzrlib.osutils import contains_whitespace
1534.9.1 by Aaron Bentley
Added progress bars to merge
24
from bzrlib.progress import DummyProgress
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
25
from bzrlib.symbol_versioning import (deprecated_function,
26
        zero_eight,
27
        )
8 by mbp at sourcefrog
store committer's timezone in revision and show
28
974.1.90 by Aaron Bentley
Switched NULL revision ID to 'null:' per robertc's suggestion
29
NULL_REVISION="null:"
974.1.89 by Aaron Bentley
Fixed merging with multiple roots, by using null as graph root.
30
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
31
class Revision(object):
1 by mbp at sourcefrog
import from baz patch-364
32
    """Single revision on a branch.
33
34
    Revisions may know their revision_hash, but only once they've been
35
    written out.  This is not stored because you cannot write the hash
36
    into the file it describes.
37
697 by Martin Pool
- write out parent list for new revisions
38
    After bzr 0.0.5 revisions are allowed to have multiple parents.
696 by Martin Pool
- Refactor revision deserialization code
39
1313 by Martin Pool
- rename to Revision.parent_ids to avoid confusion with old usage
40
    parent_ids
41
        List of parent revision_ids
1185.16.39 by Martin Pool
- constraints on revprops
42
43
    properties
44
        Dictionary of revision properties.  These are attached to the
45
        revision as extra metadata.  The name must be a single 
46
        word; the value can be an arbitrary string.
1 by mbp at sourcefrog
import from baz patch-364
47
    """
696 by Martin Pool
- Refactor revision deserialization code
48
    
1185.16.35 by Martin Pool
- stub for revision properties
49
    def __init__(self, revision_id, properties=None, **args):
1092.2.25 by Robert Collins
support ghosts in commits
50
        self.revision_id = revision_id
1185.16.35 by Martin Pool
- stub for revision properties
51
        self.properties = properties or {}
1185.16.39 by Martin Pool
- constraints on revprops
52
        self._check_properties()
1313 by Martin Pool
- rename to Revision.parent_ids to avoid confusion with old usage
53
        self.parent_ids = []
1311 by Martin Pool
- remove RevisionReference; just hold parent ids directly
54
        self.parent_sha1s = []
1733.1.4 by Robert Collins
Cosmetic niceties for debugging, extra comments etc.
55
        """Not used anymore - legacy from for 4."""
1185.42.6 by Jelmer Vernooij
Don't clear Revision.parent_ids after it has been set from the arguments
56
        self.__dict__.update(args)
696 by Martin Pool
- Refactor revision deserialization code
57
1 by mbp at sourcefrog
import from baz patch-364
58
    def __repr__(self):
184 by mbp at sourcefrog
pychecker fixups
59
        return "<Revision id %s>" % self.revision_id
1 by mbp at sourcefrog
import from baz patch-364
60
1185 by Martin Pool
- add xml round-trip test for revisions
61
    def __eq__(self, other):
62
        if not isinstance(other, Revision):
63
            return False
1092.2.20 by Robert Collins
symlink and weaves, whaddya know
64
        # FIXME: rbc 20050930 parent_ids are not being compared
65
        return (
66
                self.inventory_sha1 == other.inventory_sha1
1185 by Martin Pool
- add xml round-trip test for revisions
67
                and self.revision_id == other.revision_id
68
                and self.timestamp == other.timestamp
69
                and self.message == other.message
70
                and self.timezone == other.timezone
1185.16.35 by Martin Pool
- stub for revision properties
71
                and self.committer == other.committer
72
                and self.properties == other.properties)
1185 by Martin Pool
- add xml round-trip test for revisions
73
74
    def __ne__(self, other):
75
        return not self.__eq__(other)
76
1185.16.39 by Martin Pool
- constraints on revprops
77
    def _check_properties(self):
78
        """Verify that all revision properties are OK.
79
        """
80
        for name, value in self.properties.iteritems():
81
            if not isinstance(name, basestring) or contains_whitespace(name):
82
                raise ValueError("invalid property name %r" % name)
83
            if not isinstance(value, basestring):
84
                raise ValueError("invalid property value %r for %r" % 
85
                                 (name, value))
86
1534.4.49 by Robert Collins
Provide a revision.get_history(repository) method for generating a synthetic revision history.
87
    def get_history(self, repository):
88
        """Return the canonical line-of-history for this revision.
89
90
        If ghosts are present this may differ in result from a ghost-free
91
        repository.
92
        """
93
        current_revision = self
94
        reversed_result = []
95
        while current_revision is not None:
96
            reversed_result.append(current_revision.revision_id)
97
            if not len (current_revision.parent_ids):
98
                reversed_result.append(None)
99
                current_revision = None
100
            else:
101
                next_revision_id = current_revision.parent_ids[0]
102
                current_revision = repository.get_revision(next_revision_id)
103
        reversed_result.reverse()
104
        return reversed_result
105
1740.2.5 by Aaron Bentley
Merge from bzr.dev
106
    def get_summary(self):
107
        """Get the first line of the log message for this revision.
108
        """
109
        return self.message.split('\n', 1)[0]
110
1268 by Martin Pool
- is_ancestor now works by looking at the Branch's stored ancestry
111
112
def is_ancestor(revision_id, candidate_id, branch):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
113
    """Return true if candidate_id is an ancestor of revision_id.
1268 by Martin Pool
- is_ancestor now works by looking at the Branch's stored ancestry
114
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
115
    A false negative will be returned if any intermediate descendent of
116
    candidate_id is not present in any of the revision_sources.
810 by Martin Pool
- New validate_revision_id function
117
    
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
118
    revisions_source is an object supporting a get_revision operation that
119
    behaves like Branch's.
120
    """
1185.67.2 by Aaron Bentley
Renamed Branch.storage to Branch.repository
121
    return candidate_id in branch.repository.get_ancestry(revision_id)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
122
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
123
124
def iter_ancestors(revision_id, revision_source, only_present=False):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
125
    ancestors = (revision_id,)
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
126
    distance = 0
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
127
    while len(ancestors) > 0:
128
        new_ancestors = []
129
        for ancestor in ancestors:
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
130
            if not only_present:
131
                yield ancestor, distance
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
132
            try:
133
                revision = revision_source.get_revision(ancestor)
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
134
            except errors.NoSuchRevision, e:
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
135
                if e.revision == revision_id:
136
                    raise 
137
                else:
138
                    continue
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
139
            if only_present:
140
                yield ancestor, distance
1313 by Martin Pool
- rename to Revision.parent_ids to avoid confusion with old usage
141
            new_ancestors.extend(revision.parent_ids)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
142
        ancestors = new_ancestors
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
143
        distance += 1
144
145
146
def find_present_ancestors(revision_id, revision_source):
1133 by Martin Pool
doc
147
    """Return the ancestors of a revision present in a branch.
148
149
    It's possible that a branch won't have the complete ancestry of
150
    one of its revisions.  
151
152
    """
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
153
    found_ancestors = {}
154
    anc_iter = enumerate(iter_ancestors(revision_id, revision_source,
155
                         only_present=True))
156
    for anc_order, (anc_id, anc_distance) in anc_iter:
157
        if not found_ancestors.has_key(anc_id):
158
            found_ancestors[anc_id] = (anc_order, anc_distance)
159
    return found_ancestors
160
    
1153 by Martin Pool
- clean up some code in revision.py
161
162
def __get_closest(intersection):
163
    intersection.sort()
164
    matches = [] 
165
    for entry in intersection:
166
        if entry[0] == intersection[0][0]:
167
            matches.append(entry[2])
168
    return matches
169
974.1.35 by aaron.bentley at utoronto
Added revision-based common-ancestor checking
170
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
171
def revision_graph(revision, revision_source):
974.1.66 by Aaron Bentley
more cleanups, docs, sorting stuff
172
    """Produce a graph of the ancestry of the specified revision.
1590.1.1 by Robert Collins
Improve common_ancestor performance.
173
    
174
    :return: root, ancestors map, descendants map
974.1.66 by Aaron Bentley
more cleanups, docs, sorting stuff
175
    """
1587.1.4 by Robert Collins
Quick, urgent fix for common_ancestor performance.
176
    revision_source.lock_read()
177
    try:
178
        return _revision_graph(revision, revision_source)
179
    finally:
180
        revision_source.unlock()
181
1590.1.1 by Robert Collins
Improve common_ancestor performance.
182
1587.1.4 by Robert Collins
Quick, urgent fix for common_ancestor performance.
183
def _revision_graph(revision, revision_source):
184
    """See revision_graph."""
1590.1.1 by Robert Collins
Improve common_ancestor performance.
185
    from bzrlib.tsort import topo_sort
186
    graph = revision_source.get_revision_graph(revision)
187
    # mark all no-parent revisions as being NULL_REVISION parentage.
188
    for node, parents in graph.items():
189
        if len(parents) == 0:
190
            graph[node] = [NULL_REVISION]
191
    # add NULL_REVISION to the graph
192
    graph[NULL_REVISION] = []
193
194
    # pick a root. If there are multiple roots
195
    # this could pick a random one.
196
    topo_order = topo_sort(graph.items())
197
    root = topo_order[0]
198
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
199
    ancestors = {}
200
    descendants = {}
1590.1.1 by Robert Collins
Improve common_ancestor performance.
201
202
    # map the descendants of the graph.
203
    # and setup our set based return graph.
204
    for node in graph.keys():
205
        descendants[node] = {}
206
    for node, parents in graph.items():
207
        for parent in parents:
208
            descendants[parent][node] = 1
209
        ancestors[node] = set(parents)
210
974.1.63 by Aaron Bentley
Fixed graph-generation
211
    assert root not in descendants[root]
212
    assert root not in ancestors[root]
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
213
    return root, ancestors, descendants
214
1092.3.4 by Robert Collins
update symlink branch to integration
215
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
216
def combined_graph(revision_a, revision_b, revision_source):
974.1.66 by Aaron Bentley
more cleanups, docs, sorting stuff
217
    """Produce a combined ancestry graph.
218
    Return graph root, ancestors map, descendants map, set of common nodes"""
1590.1.1 by Robert Collins
Improve common_ancestor performance.
219
    root, ancestors, descendants = revision_graph(
220
        revision_a, revision_source)
221
    root_b, ancestors_b, descendants_b = revision_graph(
222
        revision_b, revision_source)
974.1.80 by Aaron Bentley
Improved merge error handling and testing
223
    if root != root_b:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
224
        raise errors.NoCommonRoot(revision_a, revision_b)
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
225
    common = set()
226
    for node, node_anc in ancestors_b.iteritems():
227
        if node in ancestors:
228
            common.add(node)
229
        else:
230
            ancestors[node] = set()
231
        ancestors[node].update(node_anc)
232
    for node, node_dec in descendants_b.iteritems():
233
        if node not in descendants:
1185.8.1 by Aaron Bentley
Ensured combined_graph is order-insensitive
234
            descendants[node] = {}
974.1.63 by Aaron Bentley
Fixed graph-generation
235
        descendants[node].update(node_dec)
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
236
    return root, ancestors, descendants, common
237
1092.3.4 by Robert Collins
update symlink branch to integration
238
1534.9.1 by Aaron Bentley
Added progress bars to merge
239
def common_ancestor(revision_a, revision_b, revision_source, 
240
                    pb=DummyProgress()):
1587.1.11 by Robert Collins
Local commits appear to be working properly.
241
    if None in (revision_a, revision_b):
242
        return None
1594.2.15 by Robert Collins
Unfuck performance.
243
    # trivial optimisation
244
    if revision_a == revision_b:
245
        return revision_a
974.1.80 by Aaron Bentley
Improved merge error handling and testing
246
    try:
1534.9.1 by Aaron Bentley
Added progress bars to merge
247
        try:
248
            pb.update('Picking ancestor', 1, 3)
1594.2.15 by Robert Collins
Unfuck performance.
249
            graph = revision_source.get_revision_graph_with_ghosts(
250
                [revision_a, revision_b])
251
            # convert to a NULL_REVISION based graph.
252
            ancestors = graph.get_ancestors()
253
            descendants = graph.get_descendants()
254
            common = set(graph.get_ancestry(revision_a)).intersection(
255
                     set(graph.get_ancestry(revision_b)))
256
            descendants[NULL_REVISION] = {}
257
            ancestors[NULL_REVISION] = []
258
            for root in graph.roots:
259
                descendants[NULL_REVISION][root] = 1
260
                ancestors[root].append(NULL_REVISION)
1607.1.12 by Robert Collins
Fix common_ancestor to still calculate a common ancestor when ghosts are
261
            for ghost in graph.ghosts:
262
                # ghosts act as roots for the purpose of finding 
263
                # the longest paths from the root: any ghost *might*
264
                # be directly attached to the root, so we treat them
265
                # as being such.
266
                # ghost now descends from NULL
267
                descendants[NULL_REVISION][ghost] = 1
268
                # that is it has an ancestor of NULL
269
                ancestors[ghost] = [NULL_REVISION]
270
                # ghost is common if any of ghosts descendants are common:
271
                for ghost_descendant in descendants[ghost]:
272
                    if ghost_descendant in common:
273
                        common.add(ghost)
274
                
1594.2.15 by Robert Collins
Unfuck performance.
275
            root = NULL_REVISION
276
            common.add(NULL_REVISION)
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
277
        except errors.NoCommonRoot:
278
            raise errors.NoCommonAncestor(revision_a, revision_b)
1534.9.1 by Aaron Bentley
Added progress bars to merge
279
            
280
        pb.update('Picking ancestor', 2, 3)
281
        distances = node_distances (descendants, ancestors, root)
282
        pb.update('Picking ancestor', 3, 2)
283
        farthest = select_farthest(distances, common)
284
        if farthest is None or farthest == NULL_REVISION:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
285
            raise errors.NoCommonAncestor(revision_a, revision_b)
1534.9.1 by Aaron Bentley
Added progress bars to merge
286
    finally:
287
        pb.clear()
974.1.87 by Aaron Bentley
Refactored and documented graph stuff
288
    return farthest
974.1.60 by aaron.bentley at utoronto
Initial import of common-ancestor detection
289
290
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
291
class MultipleRevisionSources(object):
1153 by Martin Pool
- clean up some code in revision.py
292
    """Proxy that looks in multiple branches for revisions."""
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
293
    def __init__(self, *args):
294
        object.__init__(self)
295
        assert len(args) != 0
296
        self._revision_sources = args
297
1590.1.1 by Robert Collins
Improve common_ancestor performance.
298
    def revision_parents(self, revision_id):
299
        for source in self._revision_sources:
300
            try:
301
                return source.revision_parents(revision_id)
302
            except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
303
                pass
304
        raise e
305
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
306
    def get_revision(self, revision_id):
307
        for source in self._revision_sources:
308
            try:
309
                return source.get_revision(revision_id)
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
310
            except errors.NoSuchRevision, e:
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
311
                pass
312
        raise e
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
313
1590.1.1 by Robert Collins
Improve common_ancestor performance.
314
    def get_revision_graph(self, revision_id):
1594.2.3 by Robert Collins
bugfix revision.MultipleRevisionSources.get_revision_graph to integrate ghosts between sources. [slow on weaves, fast on knits.
315
        # we could probe incrementally until the pending
316
        # ghosts list stop growing, but its cheaper for now
317
        # to just ask for the complete graph for each repository.
318
        graphs = []
319
        for source in self._revision_sources:
320
            ghost_graph = source.get_revision_graph_with_ghosts()
321
            graphs.append(ghost_graph)
322
        absent = 0
323
        for graph in graphs:
324
            if not revision_id in graph.get_ancestors():
325
                absent += 1
326
        if absent == len(graphs):
327
            raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
328
329
        # combine the graphs
1590.1.1 by Robert Collins
Improve common_ancestor performance.
330
        result = {}
1594.2.3 by Robert Collins
bugfix revision.MultipleRevisionSources.get_revision_graph to integrate ghosts between sources. [slow on weaves, fast on knits.
331
        pending = set([revision_id])
332
        def find_parents(node_id):
333
            """find the parents for node_id."""
334
            for graph in graphs:
335
                ancestors = graph.get_ancestors()
336
                try:
337
                    return ancestors[node_id]
338
                except KeyError:
339
                    pass
340
            raise errors.NoSuchRevision(self._revision_sources[0], node_id)
341
        while len(pending):
342
            # all the graphs should have identical parent lists
343
            node_id = pending.pop()
1590.1.1 by Robert Collins
Improve common_ancestor performance.
344
            try:
1594.2.3 by Robert Collins
bugfix revision.MultipleRevisionSources.get_revision_graph to integrate ghosts between sources. [slow on weaves, fast on knits.
345
                result[node_id] = find_parents(node_id)
346
                for parent_node in result[node_id]:
347
                    if not parent_node in result:
348
                        pending.add(parent_node)
349
            except errors.NoSuchRevision:
350
                # ghost, ignore it.
1590.1.1 by Robert Collins
Improve common_ancestor performance.
351
                pass
1594.2.3 by Robert Collins
bugfix revision.MultipleRevisionSources.get_revision_graph to integrate ghosts between sources. [slow on weaves, fast on knits.
352
        return result
1590.1.1 by Robert Collins
Improve common_ancestor performance.
353
1594.2.15 by Robert Collins
Unfuck performance.
354
    def get_revision_graph_with_ghosts(self, revision_ids):
355
        # query all the sources for their entire graphs 
356
        # and then build a combined graph for just
357
        # revision_ids.
358
        graphs = []
359
        for source in self._revision_sources:
360
            ghost_graph = source.get_revision_graph_with_ghosts()
361
            graphs.append(ghost_graph.get_ancestors())
362
        for revision_id in revision_ids:
363
            absent = 0
364
            for graph in graphs:
365
                    if not revision_id in graph:
366
                        absent += 1
367
            if absent == len(graphs):
368
                raise errors.NoSuchRevision(self._revision_sources[0],
369
                                            revision_id)
370
371
        # combine the graphs
372
        result = Graph()
373
        pending = set(revision_ids)
374
        done = set()
375
        def find_parents(node_id):
376
            """find the parents for node_id."""
377
            for graph in graphs:
378
                try:
379
                    return graph[node_id]
380
                except KeyError:
381
                    pass
382
            raise errors.NoSuchRevision(self._revision_sources[0], node_id)
383
        while len(pending):
384
            # all the graphs should have identical parent lists
385
            node_id = pending.pop()
386
            try:
387
                parents = find_parents(node_id)
388
                for parent_node in parents:
389
                    # queued or done? 
390
                    if (parent_node not in pending and
391
                        parent_node not in done):
392
                        # no, queue
393
                        pending.add(parent_node)
394
                result.add_node(node_id, parents)
395
                done.add(node_id)
396
            except errors.NoSuchRevision:
397
                # ghost
398
                result.add_ghost(node_id)
399
                continue
400
        return result
401
1587.1.4 by Robert Collins
Quick, urgent fix for common_ancestor performance.
402
    def lock_read(self):
403
        for source in self._revision_sources:
404
            source.lock_read()
405
406
    def unlock(self):
407
        for source in self._revision_sources:
408
            source.unlock()
409
1590.1.1 by Robert Collins
Improve common_ancestor performance.
410
1685.1.65 by Martin Pool
[broken] Use deprecated_function not deprecated_method
411
@deprecated_function(zero_eight)
412
def get_intervening_revisions(ancestor_id, rev_id, rev_source,
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
413
                              revision_history=None):
414
    """Find the longest line of descent from maybe_ancestor to revision.
415
    Revision history is followed where possible.
416
417
    If ancestor_id == rev_id, list will be empty.
418
    Otherwise, rev_id will be the last entry.  ancestor_id will never appear.
419
    If ancestor_id is not an ancestor, NotAncestor will be thrown
420
    """
974.1.73 by Aaron Bentley
Reimplemented get_intervening_revisions for better scalability
421
    root, ancestors, descendants = revision_graph(rev_id, rev_source)
422
    if len(descendants) == 0:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
423
        raise errors.NoSuchRevision(rev_source, rev_id)
974.1.73 by Aaron Bentley
Reimplemented get_intervening_revisions for better scalability
424
    if ancestor_id not in descendants:
425
        rev_source.get_revision(ancestor_id)
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
426
        raise errors.NotAncestor(rev_id, ancestor_id)
974.1.73 by Aaron Bentley
Reimplemented get_intervening_revisions for better scalability
427
    root_descendants = all_descendants(descendants, ancestor_id)
428
    root_descendants.add(ancestor_id)
429
    if rev_id not in root_descendants:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
430
        raise errors.NotAncestor(rev_id, ancestor_id)
974.1.73 by Aaron Bentley
Reimplemented get_intervening_revisions for better scalability
431
    distances = node_distances(descendants, ancestors, ancestor_id,
432
                               root_descendants=root_descendants)
433
434
    def best_ancestor(rev_id):
435
        best = None
436
        for anc_id in ancestors[rev_id]:
437
            try:
438
                distance = distances[anc_id]
439
            except KeyError:
440
                continue
441
            if revision_history is not None and anc_id in revision_history:
442
                return anc_id
443
            elif best is None or distance > best[1]:
444
                best = (anc_id, distance)
445
        return best[0]
446
447
    next = rev_id
448
    path = []
449
    while next != ancestor_id:
450
        path.append(next)
451
        next = best_ancestor(next)
452
    path.reverse()
453
    return path