~bzr-pqm/bzr/bzr.dev

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