/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

  • Committer: Martin Pool
  • Date: 2008-05-02 07:31:24 UTC
  • mto: (3408.2.1 integration)
  • mto: This revision was merged to the branch mainline in revision 3410.
  • Revision ID: mbp@sourcefrog.net-20080502073124-nxmeqrzkji6u2m76
Remove code deprecated prior to 1.1 and its tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# TODO: Some kind of command-line display of revision properties:
 
17
# TODO: Some kind of command-line display of revision properties: 
18
18
# perhaps show them in log -v and allow them as options to the commit command.
19
19
 
20
20
 
21
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
23
 
from bzrlib import deprecated_graph
24
 
from bzrlib import bugtracker
25
 
""")
26
21
from bzrlib import (
27
22
    errors,
28
23
    symbol_versioning,
29
24
    )
 
25
from bzrlib.deprecated_graph import (
 
26
    all_descendants,
 
27
    Graph,
 
28
    node_distances,
 
29
    select_farthest,
 
30
    )
30
31
from bzrlib.osutils import contains_whitespace
 
32
from bzrlib.progress import DummyProgress
 
33
from bzrlib.symbol_versioning import (deprecated_function,
 
34
        )
31
35
 
32
36
NULL_REVISION="null:"
33
37
CURRENT_REVISION="current:"
47
51
 
48
52
    properties
49
53
        Dictionary of revision properties.  These are attached to the
50
 
        revision as extra metadata.  The name must be a single
 
54
        revision as extra metadata.  The name must be a single 
51
55
        word; the value can be an arbitrary string.
52
56
    """
53
 
 
 
57
    
54
58
    def __init__(self, revision_id, properties=None, **args):
55
59
        self.revision_id = revision_id
56
 
        if properties is None:
57
 
            self.properties = {}
58
 
        else:
59
 
            self.properties = properties
60
 
            self._check_properties()
61
 
        self.committer = None
 
60
        self.properties = properties or {}
 
61
        self._check_properties()
62
62
        self.parent_ids = []
63
63
        self.parent_sha1s = []
64
64
        """Not used anymore - legacy from for 4."""
70
70
    def __eq__(self, other):
71
71
        if not isinstance(other, Revision):
72
72
            return False
 
73
        # FIXME: rbc 20050930 parent_ids are not being compared
73
74
        return (
74
75
                self.inventory_sha1 == other.inventory_sha1
75
76
                and self.revision_id == other.revision_id
77
78
                and self.message == other.message
78
79
                and self.timezone == other.timezone
79
80
                and self.committer == other.committer
80
 
                and self.properties == other.properties
81
 
                and self.parent_ids == other.parent_ids)
 
81
                and self.properties == other.properties)
82
82
 
83
83
    def __ne__(self, other):
84
84
        return not self.__eq__(other)
89
89
            if not isinstance(name, basestring) or contains_whitespace(name):
90
90
                raise ValueError("invalid property name %r" % name)
91
91
            if not isinstance(value, basestring):
92
 
                raise ValueError("invalid property value %r for %r" %
93
 
                                 (value, name))
 
92
                raise ValueError("invalid property value %r for %r" % 
 
93
                                 (name, value))
94
94
 
95
95
    def get_history(self, repository):
96
96
        """Return the canonical line-of-history for this revision.
113
113
 
114
114
    def get_summary(self):
115
115
        """Get the first line of the log message for this revision.
116
 
 
117
 
        Return an empty string if message is None.
118
116
        """
119
 
        if self.message:
120
 
            return self.message.lstrip().split('\n', 1)[0]
121
 
        else:
122
 
            return ''
 
117
        return self.message.lstrip().split('\n', 1)[0]
123
118
 
124
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((1, 13, 0)))
125
119
    def get_apparent_author(self):
126
120
        """Return the apparent author of this revision.
127
121
 
128
 
        This method is deprecated in favour of get_apparent_authors.
129
 
 
130
 
        If the revision properties contain any author names,
131
 
        return the first. Otherwise return the committer name.
132
 
        """
133
 
        authors = self.get_apparent_authors()
134
 
        if authors:
135
 
            return authors[0]
136
 
        else:
137
 
            return None
138
 
 
139
 
    def get_apparent_authors(self):
140
 
        """Return the apparent authors of this revision.
141
 
 
142
 
        If the revision properties contain the names of the authors,
143
 
        return them. Otherwise return the committer name.
144
 
 
145
 
        The return value will be a list containing at least one element.
146
 
        """
147
 
        authors = self.properties.get('authors', None)
148
 
        if authors is None:
149
 
            author = self.properties.get('author', self.committer)
150
 
            if author is None:
151
 
                return []
152
 
            return [author]
153
 
        else:
154
 
            return authors.split("\n")
155
 
 
156
 
    def iter_bugs(self):
157
 
        """Iterate over the bugs associated with this revision."""
158
 
        bug_property = self.properties.get('bugs', None)
159
 
        if bug_property is None:
160
 
            return
161
 
        for line in bug_property.splitlines():
162
 
            try:
163
 
                url, status = line.split(None, 2)
164
 
            except ValueError:
165
 
                raise errors.InvalidLineInBugsProperty(line)
166
 
            if status not in bugtracker.ALLOWED_BUG_STATUSES:
167
 
                raise errors.InvalidBugStatus(status)
168
 
            yield url, status
 
122
        If the revision properties contain the author name,
 
123
        return it. Otherwise return the committer name.
 
124
        """
 
125
        return self.properties.get('author', self.committer)
169
126
 
170
127
 
171
128
def iter_ancestors(revision_id, revision_source, only_present=False):
180
137
                revision = revision_source.get_revision(ancestor)
181
138
            except errors.NoSuchRevision, e:
182
139
                if e.revision == revision_id:
183
 
                    raise
 
140
                    raise 
184
141
                else:
185
142
                    continue
186
143
            if only_present:
194
151
    """Return the ancestors of a revision present in a branch.
195
152
 
196
153
    It's possible that a branch won't have the complete ancestry of
197
 
    one of its revisions.
 
154
    one of its revisions.  
198
155
 
199
156
    """
200
157
    found_ancestors = {}
204
161
        if anc_id not in found_ancestors:
205
162
            found_ancestors[anc_id] = (anc_order, anc_distance)
206
163
    return found_ancestors
207
 
 
 
164
    
208
165
 
209
166
def __get_closest(intersection):
210
167
    intersection.sort()
211
 
    matches = []
 
168
    matches = [] 
212
169
    for entry in intersection:
213
170
        if entry[0] == intersection[0][0]:
214
171
            matches.append(entry[2])
215
172
    return matches
216
173
 
217
174
 
 
175
@deprecated_function(symbol_versioning.one_four)
 
176
def revision_graph(revision, revision_source):
 
177
    """Produce a graph of the ancestry of the specified revision.
 
178
    
 
179
    :return: root, ancestors map, descendants map
 
180
    """
 
181
    revision_source.lock_read()
 
182
    try:
 
183
        return _revision_graph(revision, revision_source)
 
184
    finally:
 
185
        revision_source.unlock()
 
186
 
 
187
 
 
188
def _revision_graph(revision, revision_source):
 
189
    """See revision_graph."""
 
190
    from bzrlib.tsort import topo_sort
 
191
    graph = revision_source.get_revision_graph(revision)
 
192
    # mark all no-parent revisions as being NULL_REVISION parentage.
 
193
    for node, parents in graph.items():
 
194
        if len(parents) == 0:
 
195
            graph[node] = [NULL_REVISION]
 
196
    # add NULL_REVISION to the graph
 
197
    graph[NULL_REVISION] = []
 
198
 
 
199
    # pick a root. If there are multiple roots
 
200
    # this could pick a random one.
 
201
    topo_order = topo_sort(graph.items())
 
202
    root = topo_order[0]
 
203
 
 
204
    ancestors = {}
 
205
    descendants = {}
 
206
 
 
207
    # map the descendants of the graph.
 
208
    # and setup our set based return graph.
 
209
    for node in graph.keys():
 
210
        descendants[node] = {}
 
211
    for node, parents in graph.items():
 
212
        for parent in parents:
 
213
            descendants[parent][node] = 1
 
214
        ancestors[node] = set(parents)
 
215
 
 
216
    assert root not in descendants[root]
 
217
    assert root not in ancestors[root]
 
218
    return root, ancestors, descendants
 
219
 
 
220
 
 
221
@deprecated_function(symbol_versioning.one_three)
 
222
def combined_graph(revision_a, revision_b, revision_source):
 
223
    """Produce a combined ancestry graph.
 
224
    Return graph root, ancestors map, descendants map, set of common nodes"""
 
225
    root, ancestors, descendants = revision_graph(
 
226
        revision_a, revision_source)
 
227
    root_b, ancestors_b, descendants_b = revision_graph(
 
228
        revision_b, revision_source)
 
229
    if root != root_b:
 
230
        raise errors.NoCommonRoot(revision_a, revision_b)
 
231
    common = set()
 
232
    for node, node_anc in ancestors_b.iteritems():
 
233
        if node in ancestors:
 
234
            common.add(node)
 
235
        else:
 
236
            ancestors[node] = set()
 
237
        ancestors[node].update(node_anc)
 
238
    for node, node_dec in descendants_b.iteritems():
 
239
        if node not in descendants:
 
240
            descendants[node] = {}
 
241
        descendants[node].update(node_dec)
 
242
    return root, ancestors, descendants, common
 
243
 
 
244
 
 
245
@deprecated_function(symbol_versioning.one_three)
 
246
def common_ancestor(revision_a, revision_b, revision_source, 
 
247
                    pb=DummyProgress()):
 
248
    if None in (revision_a, revision_b):
 
249
        return None
 
250
    if NULL_REVISION in (revision_a, revision_b):
 
251
        return NULL_REVISION
 
252
    # trivial optimisation
 
253
    if revision_a == revision_b:
 
254
        return revision_a
 
255
    try:
 
256
        try:
 
257
            pb.update('Picking ancestor', 1, 3)
 
258
            graph = revision_source.get_revision_graph_with_ghosts(
 
259
                [revision_a, revision_b])
 
260
            # Shortcut the case where one of the tips is already included in
 
261
            # the other graphs ancestry.
 
262
            ancestry_a = graph.get_ancestry(revision_a, topo_sorted=False)
 
263
            if revision_b in ancestry_a:
 
264
                return revision_b
 
265
            ancestry_b = graph.get_ancestry(revision_b, topo_sorted=False)
 
266
            if revision_a in ancestry_b:
 
267
                return revision_a
 
268
            # convert to a NULL_REVISION based graph.
 
269
            ancestors = graph.get_ancestors()
 
270
            descendants = graph.get_descendants()
 
271
            common = set(ancestry_a)
 
272
            common.intersection_update(ancestry_b)
 
273
            descendants[NULL_REVISION] = {}
 
274
            ancestors[NULL_REVISION] = []
 
275
            for root in graph.roots:
 
276
                descendants[NULL_REVISION][root] = 1
 
277
                ancestors[root].append(NULL_REVISION)
 
278
            for ghost in graph.ghosts:
 
279
                # ghosts act as roots for the purpose of finding 
 
280
                # the longest paths from the root: any ghost *might*
 
281
                # be directly attached to the root, so we treat them
 
282
                # as being such.
 
283
                # ghost now descends from NULL
 
284
                descendants[NULL_REVISION][ghost] = 1
 
285
                # that is it has an ancestor of NULL
 
286
                ancestors[ghost] = [NULL_REVISION]
 
287
                # ghost is common if any of ghosts descendants are common:
 
288
                for ghost_descendant in descendants[ghost]:
 
289
                    if ghost_descendant in common:
 
290
                        common.add(ghost)
 
291
                
 
292
            root = NULL_REVISION
 
293
            common.add(NULL_REVISION)
 
294
        except errors.NoCommonRoot:
 
295
            raise errors.NoCommonAncestor(revision_a, revision_b)
 
296
            
 
297
        pb.update('Picking ancestor', 2, 3)
 
298
        distances = node_distances (descendants, ancestors, root)
 
299
        pb.update('Picking ancestor', 3, 2)
 
300
        farthest = select_farthest(distances, common)
 
301
        if farthest is None or farthest == NULL_REVISION:
 
302
            raise errors.NoCommonAncestor(revision_a, revision_b)
 
303
    finally:
 
304
        pb.clear()
 
305
    return farthest
 
306
 
 
307
 
 
308
class MultipleRevisionSources(object):
 
309
    """Proxy that looks in multiple branches for revisions."""
 
310
 
 
311
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
 
312
    def __init__(self, *args):
 
313
        object.__init__(self)
 
314
        assert len(args) != 0
 
315
        self._revision_sources = args
 
316
 
 
317
    def revision_parents(self, revision_id):
 
318
        for source in self._revision_sources:
 
319
            try:
 
320
                return source.revision_parents(revision_id)
 
321
            except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
 
322
                pass
 
323
        raise e
 
324
 
 
325
    def get_revision(self, revision_id):
 
326
        for source in self._revision_sources:
 
327
            try:
 
328
                return source.get_revision(revision_id)
 
329
            except errors.NoSuchRevision, e:
 
330
                pass
 
331
        raise e
 
332
 
 
333
    def get_revision_graph(self, revision_id):
 
334
        # we could probe incrementally until the pending
 
335
        # ghosts list stop growing, but its cheaper for now
 
336
        # to just ask for the complete graph for each repository.
 
337
        graphs = []
 
338
        for source in self._revision_sources:
 
339
            ghost_graph = source.get_revision_graph_with_ghosts()
 
340
            graphs.append(ghost_graph)
 
341
        absent = 0
 
342
        for graph in graphs:
 
343
            if not revision_id in graph.get_ancestors():
 
344
                absent += 1
 
345
        if absent == len(graphs):
 
346
            raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
 
347
 
 
348
        # combine the graphs
 
349
        result = {}
 
350
        pending = set([revision_id])
 
351
        def find_parents(node_id):
 
352
            """find the parents for node_id."""
 
353
            for graph in graphs:
 
354
                ancestors = graph.get_ancestors()
 
355
                try:
 
356
                    return ancestors[node_id]
 
357
                except KeyError:
 
358
                    pass
 
359
            raise errors.NoSuchRevision(self._revision_sources[0], node_id)
 
360
        while len(pending):
 
361
            # all the graphs should have identical parent lists
 
362
            node_id = pending.pop()
 
363
            try:
 
364
                result[node_id] = find_parents(node_id)
 
365
                for parent_node in result[node_id]:
 
366
                    if not parent_node in result:
 
367
                        pending.add(parent_node)
 
368
            except errors.NoSuchRevision:
 
369
                # ghost, ignore it.
 
370
                pass
 
371
        return result
 
372
 
 
373
    def get_revision_graph_with_ghosts(self, revision_ids):
 
374
        # query all the sources for their entire graphs 
 
375
        # and then build a combined graph for just
 
376
        # revision_ids.
 
377
        graphs = []
 
378
        for source in self._revision_sources:
 
379
            ghost_graph = source.get_revision_graph_with_ghosts()
 
380
            graphs.append(ghost_graph.get_ancestors())
 
381
        for revision_id in revision_ids:
 
382
            absent = 0
 
383
            for graph in graphs:
 
384
                    if not revision_id in graph:
 
385
                        absent += 1
 
386
            if absent == len(graphs):
 
387
                raise errors.NoSuchRevision(self._revision_sources[0],
 
388
                                            revision_id)
 
389
 
 
390
        # combine the graphs
 
391
        result = Graph()
 
392
        pending = set(revision_ids)
 
393
        done = set()
 
394
        def find_parents(node_id):
 
395
            """find the parents for node_id."""
 
396
            for graph in graphs:
 
397
                try:
 
398
                    return graph[node_id]
 
399
                except KeyError:
 
400
                    pass
 
401
            raise errors.NoSuchRevision(self._revision_sources[0], node_id)
 
402
        while len(pending):
 
403
            # all the graphs should have identical parent lists
 
404
            node_id = pending.pop()
 
405
            try:
 
406
                parents = find_parents(node_id)
 
407
                for parent_node in parents:
 
408
                    # queued or done? 
 
409
                    if (parent_node not in pending and
 
410
                        parent_node not in done):
 
411
                        # no, queue
 
412
                        pending.add(parent_node)
 
413
                result.add_node(node_id, parents)
 
414
                done.add(node_id)
 
415
            except errors.NoSuchRevision:
 
416
                # ghost
 
417
                result.add_ghost(node_id)
 
418
                continue
 
419
        return result
 
420
 
 
421
    def lock_read(self):
 
422
        for source in self._revision_sources:
 
423
            source.lock_read()
 
424
 
 
425
    def unlock(self):
 
426
        for source in self._revision_sources:
 
427
            source.unlock()
 
428
 
 
429
 
218
430
def is_reserved_id(revision_id):
219
431
    """Determine whether a revision id is reserved
220
432
 
221
 
    :return: True if the revision is reserved, False otherwise
 
433
    :return: True if the revision is is reserved, False otherwise
222
434
    """
223
435
    return isinstance(revision_id, basestring) and revision_id.endswith(':')
224
436