/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/smart/versionedfiles.py

  • Committer: Andrew Bennetts
  • Date: 2008-11-20 10:30:56 UTC
  • mfrom: (3695.2.6 hpss-push-rpc)
  • mto: This revision was merged to the branch mainline in revision 3981.
  • Revision ID: andrew.bennetts@canonical.com-20081120103056-05g6c6nv30ceyxdx
Merge RemoteVersionedFiles class from hpss-push-rpc.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Server-side versioned files related request implmentations."""
 
18
 
 
19
import bz2
 
20
from cStringIO import StringIO
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    graph,
 
25
    )
 
26
from bzrlib.bzrdir import BzrDir
 
27
from bzrlib.smart.request import (
 
28
    FailedSmartServerResponse,
 
29
    SmartServerRequest,
 
30
    SuccessfulSmartServerResponse,
 
31
    )
 
32
from bzrlib.smart.repository import SmartServerRepositoryRequest
 
33
from bzrlib.repository import _strip_NULL_ghosts
 
34
from bzrlib import revision as _mod_revision
 
35
 
 
36
 
 
37
class SmartServerVersionedFilesRequest(SmartServerRepositoryRequest):
 
38
    """Common base class for Repository requests."""
 
39
 
 
40
    def do(self, path, *args):
 
41
        """Execute a repository request.
 
42
        
 
43
        All Repository requests take a path to the repository as their first
 
44
        argument.  The repository must be at the exact path given by the
 
45
        client - no searching is done.
 
46
 
 
47
        The actual logic is delegated to self.do_repository_request.
 
48
 
 
49
        :param client_path: The path for the repository as received from the
 
50
            client.
 
51
        :return: A SmartServerResponse from self.do_repository_request().
 
52
        """
 
53
        transport = self.transport_from_client_path(path)
 
54
        bzrdir = BzrDir.open_from_transport(transport)
 
55
        # Save the repository for use with do_body.
 
56
        self._repository = bzrdir.open_repository()
 
57
        return self.do_repository_request(self._repository, *args)
 
58
 
 
59
    def do_repository_request(self, repository, vf_name, *args):
 
60
        if vf_name not in ['texts', 'inventories', 'signatures', 'revisions']:
 
61
            return FailedSmartServerResponse(('NoSuchVersionedFile', vf_name))
 
62
        versioned_files = getattr(repository, vf_name)
 
63
        return self.do_versioned_files_request(versioned_files, *args)
 
64
 
 
65
    def do_versioned_files_request(self, versioned_files, *args):
 
66
        """Override to provide an implementation for a verb."""
 
67
 
 
68
 
 
69
class SmartServerVersionedFilesGetParentMap(SmartServerVersionedFilesRequest):
 
70
    """Bzr 1.2+ - get parent data for revisions during a graph search."""
 
71
    
 
72
    def do_versioned_files_request(self, versioned_files, *keys):
 
73
        """Get parent details for some revisions.
 
74
        
 
75
        All the parents for revision_ids are returned. Additionally up to 64KB
 
76
        of additional parent data found by performing a breadth first search
 
77
        from revision_ids is returned. The verb takes a body containing the
 
78
        current search state, see do_body for details.
 
79
 
 
80
        :param repository: The repository to query in.
 
81
        :param revision_ids: The utf8 encoded revision_id to answer for.
 
82
        """
 
83
        self._keys = tuple(tuple(key) for key in keys)
 
84
        self._versioned_files = versioned_files
 
85
        return None # Signal that we want a body.
 
86
 
 
87
    def do_body(self, body_bytes):
 
88
        """Process the current search state and perform the parent lookup.
 
89
 
 
90
        :return: A smart server response where the body contains an utf8
 
91
            encoded flattened list of the parents of the keys which has been
 
92
            bz2 compressed.
 
93
        """
 
94
        repository = self._repository
 
95
        repository.lock_read()
 
96
        try:
 
97
            vf_graph = graph.Graph(self._versioned_files)
 
98
            return self._do_repository_request(body_bytes, vf_graph)
 
99
        finally:
 
100
            repository.unlock()
 
101
 
 
102
    def _deserialise_search_tuple_key_recipe(self, bytes):
 
103
        start_keys_bytes, stop_keys_bytes, count_bytes = bytes.split('\n')
 
104
        start_keys = [tuple(k.split(' ')) for k in start_keys_bytes.split('\0')]
 
105
        stop_keys = [tuple(k.split(' ')) for k in stop_keys_bytes.split('\0')]
 
106
        count = int(count_bytes)
 
107
        return tuple(start_keys), set(stop_keys), count
 
108
 
 
109
    def recreate_vf_search(self, vf_graph, recipe_bytes):
 
110
        recipe = self._deserialise_search_tuple_key_recipe(recipe_bytes)
 
111
        start_keys, exclude_keys, key_count = recipe
 
112
        # lock_read
 
113
        try:
 
114
            search = vf_graph._make_breadth_first_searcher(start_keys)
 
115
            while True:
 
116
                try:
 
117
                    next_revs = search.next()
 
118
                except StopIteration:
 
119
                    break
 
120
                search.stop_searching_any(exclude_keys.intersection(next_revs))
 
121
            search_result = search.get_result()
 
122
            if search_result.get_recipe()[2] != key_count:
 
123
                # we got back a different amount of data than expected, this
 
124
                # gets reported as NoSuchRevision, because less revisions
 
125
                # indicates missing revisions, and more should never happen as
 
126
                # the excludes list considers ghosts and ensures that ghost
 
127
                # filling races are not a problem.
 
128
                return (None, FailedSmartServerResponse(('NoSuchRevision',)))
 
129
            return (search, None)
 
130
        finally:
 
131
            pass
 
132
            # unlock
 
133
            
 
134
    def _do_repository_request(self, body_bytes, vf_graph):
 
135
        repository = self._repository
 
136
        keys = set(self._keys)
 
137
        search, error = self.recreate_vf_search(vf_graph, body_bytes)
 
138
        if error is not None:
 
139
            return error
 
140
        # TODO might be nice to start up the search again; but thats not
 
141
        # written or tested yet.
 
142
        client_seen_keys = set(search.get_result().get_keys())
 
143
        # Always include the requested ids.
 
144
        client_seen_keys.difference_update(keys)
 
145
        lines = []
 
146
        result = {}
 
147
        queried_keys = set()
 
148
        size_so_far = 0
 
149
        next_keys = keys
 
150
        first_loop_done = False
 
151
        while next_keys:
 
152
            queried_keys.update(next_keys)
 
153
            parent_map = vf_graph.get_parent_map(next_keys)
 
154
            next_keys = set()
 
155
            for key, parents in parent_map.iteritems():
 
156
                # prepare the next query
 
157
                next_keys.update(parents)
 
158
                if key not in client_seen_keys:
 
159
                    # Client does not have this revision, give it to it.
 
160
                    # add parents to the result
 
161
                    result[key] = parents
 
162
                    # Approximate the serialized cost of this key.
 
163
                    # XXX: this approximation is out of date
 
164
                    size_so_far += 2 + len(key) + sum(map(len, parents))
 
165
            # get all the directly asked for parents, and then flesh out to
 
166
            # 64K (compressed) or so. We do one level of depth at a time to
 
167
            # stay in sync with the client. The 250000 magic number is
 
168
            # estimated compression ratio taken from bzr.dev itself.
 
169
            if first_loop_done and size_so_far > 250000:
 
170
                next_keys = set()
 
171
                break
 
172
            # don't query things we've already queried
 
173
            next_keys.difference_update(queried_keys)
 
174
            first_loop_done = True
 
175
 
 
176
        # sorting trivially puts lexographically similar revision ids together.
 
177
        # Compression FTW.
 
178
        result = sorted(result.items())
 
179
 
 
180
        return SuccessfulSmartServerResponse(
 
181
            ('ok', ), bz2.compress(_serialise_search_result(result)))
 
182
 
 
183
 
 
184
def _serialise_search_result(result_items):
 
185
    buf = StringIO()
 
186
    first = True
 
187
    for key, parents in result_items:
 
188
        if first:
 
189
            first = False
 
190
        else:
 
191
            buf.write('\n')
 
192
        buf.write(' '.join(key))
 
193
        buf.write('\0')
 
194
        parents_iter = (' '.join(parent) for parent in parents)
 
195
        buf.write('\0'.join(parents_iter))
 
196
    return buf.getvalue()