/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/repository.py

 * ``Repository.get_data_stream`` is now deprecated in favour of
   ``Repository.get_data_stream_for_search`` which allows less network
   traffic when requesting data streams over a smart server. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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 repository related request implmentations."""
 
18
 
 
19
from cStringIO import StringIO
 
20
import os
 
21
import sys
 
22
import tempfile
 
23
import tarfile
 
24
 
 
25
from bzrlib import errors
 
26
from bzrlib.bzrdir import BzrDir
 
27
from bzrlib.pack import ContainerSerialiser
 
28
from bzrlib.smart.request import (
 
29
    FailedSmartServerResponse,
 
30
    SmartServerRequest,
 
31
    SuccessfulSmartServerResponse,
 
32
    )
 
33
from bzrlib import revision as _mod_revision
 
34
 
 
35
 
 
36
class SmartServerRepositoryRequest(SmartServerRequest):
 
37
    """Common base class for Repository requests."""
 
38
 
 
39
    def do(self, path, *args):
 
40
        """Execute a repository request.
 
41
        
 
42
        The repository must be at the exact path - no searching is done.
 
43
 
 
44
        The actual logic is delegated to self.do_repository_request.
 
45
 
 
46
        :param path: The path for the repository.
 
47
        :return: A smart server from self.do_repository_request().
 
48
        """
 
49
        transport = self._backing_transport.clone(path)
 
50
        bzrdir = BzrDir.open_from_transport(transport)
 
51
        repository = bzrdir.open_repository()
 
52
        return self.do_repository_request(repository, *args)
 
53
 
 
54
 
 
55
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
 
56
    
 
57
    def do_repository_request(self, repository, *revision_ids):
 
58
        repository.lock_read()
 
59
        try:
 
60
            return self._do_repository_request(repository, revision_ids)
 
61
        finally:
 
62
            repository.unlock()
 
63
 
 
64
    def _do_repository_request(self, repository, revision_ids):
 
65
        """Get parent details for some revisions.
 
66
        
 
67
        All the parents for revision_ids are returned. Additionally up to 64KB
 
68
        of additional parent data found by performing a breadth first search
 
69
        from revision_ids is returned.
 
70
 
 
71
        :param repository: The repository to query in.
 
72
        :param revision_ids: The utf8 encoded revision_id to answer for.
 
73
        :return: A smart server response where the body contains an utf8
 
74
            encoded flattened list of the parents of the revisions, (the same
 
75
            format as Repository.get_revision_graph).
 
76
        """
 
77
        lines = []
 
78
        repo_graph = repository.get_graph()
 
79
        result = {}
 
80
        queried_revs = set()
 
81
        size_so_far = 0
 
82
        next_revs = revision_ids
 
83
        first_loop_done = False
 
84
        while next_revs:
 
85
            queried_revs.update(next_revs)
 
86
            parent_map = repo_graph.get_parent_map(next_revs)
 
87
            next_revs = set()
 
88
            for revision_id, parents in parent_map.iteritems():
 
89
                # adjust for the wire
 
90
                if parents == (_mod_revision.NULL_REVISION,):
 
91
                    parents = ()
 
92
                # add parents to the result
 
93
                result[revision_id] = parents
 
94
                # prepare the next query
 
95
                next_revs.update(parents)
 
96
                # Approximate the serialized cost of this revision_id.
 
97
                size_so_far += 2 + len(revision_id) + sum(map(len, parents))
 
98
                # get all the directly asked for parents, and then flesh out to
 
99
                # 64K or so.
 
100
                if first_loop_done and size_so_far > 65000:
 
101
                    next_revs = set()
 
102
                    break
 
103
            # don't query things we've already queried
 
104
            next_revs.difference_update(queried_revs)
 
105
            first_loop_done = True
 
106
 
 
107
        for revision, parents in result.items():
 
108
            lines.append(' '.join((revision, ) + tuple(parents)))
 
109
 
 
110
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
 
111
 
 
112
 
 
113
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
 
114
    
 
115
    def do_repository_request(self, repository, revision_id):
 
116
        """Return the result of repository.get_revision_graph(revision_id).
 
117
        
 
118
        :param repository: The repository to query in.
 
119
        :param revision_id: The utf8 encoded revision_id to get a graph from.
 
120
        :return: A smart server response where the body contains an utf8
 
121
            encoded flattened list of the revision graph.
 
122
        """
 
123
        if not revision_id:
 
124
            revision_id = None
 
125
 
 
126
        lines = []
 
127
        try:
 
128
            revision_graph = repository.get_revision_graph(revision_id)
 
129
        except errors.NoSuchRevision:
 
130
            # Note that we return an empty body, rather than omitting the body.
 
131
            # This way the client knows that it can always expect to find a body
 
132
            # in the response for this method, even in the error case.
 
133
            return FailedSmartServerResponse(('nosuchrevision', revision_id), '')
 
134
 
 
135
        for revision, parents in revision_graph.items():
 
136
            lines.append(' '.join((revision, ) + tuple(parents)))
 
137
 
 
138
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
 
139
 
 
140
 
 
141
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
 
142
 
 
143
    def do_repository_request(self, repository, revision_id):
 
144
        """Return ok if a specific revision is in the repository at path.
 
145
 
 
146
        :param repository: The repository to query in.
 
147
        :param revision_id: The utf8 encoded revision_id to lookup.
 
148
        :return: A smart server response of ('ok', ) if the revision is
 
149
            present.
 
150
        """
 
151
        if repository.has_revision(revision_id):
 
152
            return SuccessfulSmartServerResponse(('yes', ))
 
153
        else:
 
154
            return SuccessfulSmartServerResponse(('no', ))
 
155
 
 
156
 
 
157
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
 
158
 
 
159
    def do_repository_request(self, repository, revid, committers):
 
160
        """Return the result of repository.gather_stats().
 
161
 
 
162
        :param repository: The repository to query in.
 
163
        :param revid: utf8 encoded rev id or an empty string to indicate None
 
164
        :param committers: 'yes' or 'no'.
 
165
 
 
166
        :return: A SmartServerResponse ('ok',), a encoded body looking like
 
167
              committers: 1
 
168
              firstrev: 1234.230 0
 
169
              latestrev: 345.700 3600
 
170
              revisions: 2
 
171
              size:45
 
172
 
 
173
              But containing only fields returned by the gather_stats() call
 
174
        """
 
175
        if revid == '':
 
176
            decoded_revision_id = None
 
177
        else:
 
178
            decoded_revision_id = revid
 
179
        if committers == 'yes':
 
180
            decoded_committers = True
 
181
        else:
 
182
            decoded_committers = None
 
183
        stats = repository.gather_stats(decoded_revision_id, decoded_committers)
 
184
 
 
185
        body = ''
 
186
        if stats.has_key('committers'):
 
187
            body += 'committers: %d\n' % stats['committers']
 
188
        if stats.has_key('firstrev'):
 
189
            body += 'firstrev: %.3f %d\n' % stats['firstrev']
 
190
        if stats.has_key('latestrev'):
 
191
             body += 'latestrev: %.3f %d\n' % stats['latestrev']
 
192
        if stats.has_key('revisions'):
 
193
            body += 'revisions: %d\n' % stats['revisions']
 
194
        if stats.has_key('size'):
 
195
            body += 'size: %d\n' % stats['size']
 
196
 
 
197
        return SuccessfulSmartServerResponse(('ok', ), body)
 
198
 
 
199
 
 
200
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
 
201
 
 
202
    def do_repository_request(self, repository):
 
203
        """Return the result of repository.is_shared().
 
204
 
 
205
        :param repository: The repository to query in.
 
206
        :return: A smart server response of ('yes', ) if the repository is
 
207
            shared, and ('no', ) if it is not.
 
208
        """
 
209
        if repository.is_shared():
 
210
            return SuccessfulSmartServerResponse(('yes', ))
 
211
        else:
 
212
            return SuccessfulSmartServerResponse(('no', ))
 
213
 
 
214
 
 
215
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
 
216
 
 
217
    def do_repository_request(self, repository, token=''):
 
218
        # XXX: this probably should not have a token.
 
219
        if token == '':
 
220
            token = None
 
221
        try:
 
222
            token = repository.lock_write(token=token)
 
223
        except errors.LockContention, e:
 
224
            return FailedSmartServerResponse(('LockContention',))
 
225
        except errors.UnlockableTransport:
 
226
            return FailedSmartServerResponse(('UnlockableTransport',))
 
227
        except errors.LockFailed, e:
 
228
            return FailedSmartServerResponse(('LockFailed',
 
229
                str(e.lock), str(e.why)))
 
230
        if token is not None:
 
231
            repository.leave_lock_in_place()
 
232
        repository.unlock()
 
233
        if token is None:
 
234
            token = ''
 
235
        return SuccessfulSmartServerResponse(('ok', token))
 
236
 
 
237
 
 
238
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
 
239
 
 
240
    def do_repository_request(self, repository, token):
 
241
        try:
 
242
            repository.lock_write(token=token)
 
243
        except errors.TokenMismatch, e:
 
244
            return FailedSmartServerResponse(('TokenMismatch',))
 
245
        repository.dont_leave_lock_in_place()
 
246
        repository.unlock()
 
247
        return SuccessfulSmartServerResponse(('ok',))
 
248
 
 
249
 
 
250
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
 
251
    """Get the raw repository files as a tarball.
 
252
 
 
253
    The returned tarball contains a .bzr control directory which in turn
 
254
    contains a repository.
 
255
    
 
256
    This takes one parameter, compression, which currently must be 
 
257
    "", "gz", or "bz2".
 
258
 
 
259
    This is used to implement the Repository.copy_content_into operation.
 
260
    """
 
261
 
 
262
    def do_repository_request(self, repository, compression):
 
263
        from bzrlib import osutils
 
264
        repo_transport = repository.control_files._transport
 
265
        tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
 
266
        try:
 
267
            controldir_name = tmp_dirname + '/.bzr'
 
268
            return self._tarfile_response(controldir_name, compression)
 
269
        finally:
 
270
            osutils.rmtree(tmp_dirname)
 
271
 
 
272
    def _copy_to_tempdir(self, from_repo):
 
273
        tmp_dirname = tempfile.mkdtemp(prefix='tmpbzrclone')
 
274
        tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
 
275
        tmp_repo = from_repo._format.initialize(tmp_bzrdir)
 
276
        from_repo.copy_content_into(tmp_repo)
 
277
        return tmp_dirname, tmp_repo
 
278
 
 
279
    def _tarfile_response(self, tmp_dirname, compression):
 
280
        temp = tempfile.NamedTemporaryFile()
 
281
        try:
 
282
            self._tarball_of_dir(tmp_dirname, compression, temp.file)
 
283
            # all finished; write the tempfile out to the network
 
284
            temp.seek(0)
 
285
            return SuccessfulSmartServerResponse(('ok',), temp.read())
 
286
            # FIXME: Don't read the whole thing into memory here; rather stream it
 
287
            # out from the file onto the network. mbp 20070411
 
288
        finally:
 
289
            temp.close()
 
290
 
 
291
    def _tarball_of_dir(self, dirname, compression, ofile):
 
292
        filename = os.path.basename(ofile.name)
 
293
        tarball = tarfile.open(fileobj=ofile, name=filename,
 
294
            mode='w|' + compression)
 
295
        try:
 
296
            # The tarball module only accepts ascii names, and (i guess)
 
297
            # packs them with their 8bit names.  We know all the files
 
298
            # within the repository have ASCII names so the should be safe
 
299
            # to pack in.
 
300
            dirname = dirname.encode(sys.getfilesystemencoding())
 
301
            # python's tarball module includes the whole path by default so
 
302
            # override it
 
303
            assert dirname.endswith('.bzr')
 
304
            tarball.add(dirname, '.bzr') # recursive by default
 
305
        finally:
 
306
            tarball.close()
 
307
 
 
308
 
 
309
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
 
310
 
 
311
    def do_repository_request(self, repository, *revision_ids):
 
312
        repository.lock_read()
 
313
        try:
 
314
            return self._do_repository_request(repository, revision_ids)
 
315
        finally:
 
316
            repository.unlock()
 
317
 
 
318
    def _do_repository_request(self, repository, revision_ids):
 
319
        stream = repository.get_data_stream_for_search(
 
320
            repository.revision_ids_to_search_result(set(revision_ids)))
 
321
        buffer = StringIO()
 
322
        pack = ContainerSerialiser()
 
323
        buffer.write(pack.begin())
 
324
        try:
 
325
            for name_tuple, bytes in stream:
 
326
                buffer.write(pack.bytes_record(bytes, [name_tuple]))
 
327
        except errors.RevisionNotPresent, e:
 
328
            return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
 
329
        buffer.write(pack.end())
 
330
        return SuccessfulSmartServerResponse(('ok',), buffer.getvalue())
 
331
 
 
332
 
 
333
class SmartServerRepositoryStreamRevisionsChunked(SmartServerRepositoryRequest):
 
334
 
 
335
    def do_repository_request(self, repository, *revision_ids):
 
336
        repository.lock_read()
 
337
        try:
 
338
            stream = repository.get_data_stream_for_search(
 
339
                repository.revision_ids_to_search_result(set(revision_ids)))
 
340
        except Exception:
 
341
            repository.unlock()
 
342
            raise
 
343
        return SuccessfulSmartServerResponse(('ok',),
 
344
            body_stream=self.body_stream(stream, repository))
 
345
 
 
346
    def body_stream(self, stream, repository):
 
347
        pack = ContainerSerialiser()
 
348
        yield pack.begin()
 
349
        try:
 
350
            for name_tuple, bytes in stream:
 
351
                yield pack.bytes_record(bytes, [name_tuple])
 
352
        except errors.RevisionNotPresent, e:
 
353
            yield FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
 
354
        repository.unlock()
 
355
        pack.end()
 
356