~bzr-pqm/bzr/bzr.dev

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