~bzr-pqm/bzr/bzr.dev

2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
1
# Copyright (C) 2006, 2007 Canonical Ltd
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
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
3211.5.2 by Robert Collins
Change RemoteRepository.get_parent_map to use bz2 not gzip for compression.
19
import bz2
2535.3.14 by Andrew Bennetts
Move serialising repo data stream to bytes into smart protocol.
20
from cStringIO import StringIO
2571.2.2 by Ian Clatworthy
use basename as poolie recommended
21
import os
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
22
import sys
23
import tempfile
24
import tarfile
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
25
26
from bzrlib import errors
27
from bzrlib.bzrdir import BzrDir
2535.4.30 by Andrew Bennetts
Use ContainerSerialiser rather than ContainerWriter in bzrlib/smart/repository.py.
28
from bzrlib.pack import ContainerSerialiser
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
29
from bzrlib.smart.request import (
30
    FailedSmartServerResponse,
31
    SmartServerRequest,
32
    SuccessfulSmartServerResponse,
33
    )
3287.6.8 by Robert Collins
Reduce code duplication as per review.
34
from bzrlib.repository import _strip_NULL_ghosts
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
35
from bzrlib import revision as _mod_revision
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
36
37
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
38
class SmartServerRepositoryRequest(SmartServerRequest):
39
    """Common base class for Repository requests."""
40
41
    def do(self, path, *args):
42
        """Execute a repository request.
43
        
2692.1.10 by Andrew Bennetts
More docstring polish
44
        All Repository requests take a path to the repository as their first
45
        argument.  The repository must be at the exact path given by the
46
        client - no searching is done.
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
47
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
48
        The actual logic is delegated to self.do_repository_request.
49
2692.1.10 by Andrew Bennetts
More docstring polish
50
        :param client_path: The path for the repository as received from the
51
            client.
52
        :return: A SmartServerResponse from self.do_repository_request().
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
53
        """
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
54
        transport = self.transport_from_client_path(path)
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
55
        bzrdir = BzrDir.open_from_transport(transport)
3184.1.10 by Robert Collins
Change the smart server verb for Repository.stream_revisions_chunked to use SearchResults as the request mechanism for downloads.
56
        # Save the repository for use with do_body.
57
        self._repository = bzrdir.open_repository()
58
        return self.do_repository_request(self._repository, *args)
59
60
    def do_repository_request(self, repository, *args):
61
        """Override to provide an implementation for a verb."""
62
        # No-op for verbs that take bodies (None as a result indicates a body
63
        # is expected)
64
        return None
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
65
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
66
    def recreate_search(self, repository, recipe_bytes):
67
        lines = recipe_bytes.split('\n')
68
        start_keys = set(lines[0].split(' '))
69
        exclude_keys = set(lines[1].split(' '))
70
        revision_count = int(lines[2])
71
        repository.lock_read()
72
        try:
73
            search = repository.get_graph()._make_breadth_first_searcher(
74
                start_keys)
75
            while True:
76
                try:
77
                    next_revs = search.next()
78
                except StopIteration:
79
                    break
80
                search.stop_searching_any(exclude_keys.intersection(next_revs))
81
            search_result = search.get_result()
82
            if search_result.get_recipe()[2] != revision_count:
83
                # we got back a different amount of data than expected, this
84
                # gets reported as NoSuchRevision, because less revisions
85
                # indicates missing revisions, and more should never happen as
86
                # the excludes list considers ghosts and ensures that ghost
87
                # filling races are not a problem.
88
                return (None, FailedSmartServerResponse(('NoSuchRevision',)))
89
            return (search, None)
90
        finally:
91
            repository.unlock()
92
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
93
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
94
class SmartServerRepositoryReadLocked(SmartServerRepositoryRequest):
95
    """Calls self.do_readlocked_repository_request."""
96
97
    def do_repository_request(self, repository, *args):
98
        """Read lock a repository for do_readlocked_repository_request."""
99
        repository.lock_read()
100
        try:
101
            return self.do_readlocked_repository_request(repository, *args)
102
        finally:
103
            repository.unlock()
104
105
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
106
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
107
    """Bzr 1.2+ - get parent data for revisions during a graph search."""
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
108
    
109
    def do_repository_request(self, repository, *revision_ids):
110
        """Get parent details for some revisions.
111
        
112
        All the parents for revision_ids are returned. Additionally up to 64KB
113
        of additional parent data found by performing a breadth first search
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
114
        from revision_ids is returned. The verb takes a body containing the
115
        current search state, see do_body for details.
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
116
117
        :param repository: The repository to query in.
3172.5.8 by Robert Collins
Review feedback.
118
        :param revision_ids: The utf8 encoded revision_id to answer for.
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
119
        """
120
        self._revision_ids = revision_ids
121
        return None # Signal that we want a body.
122
123
    def do_body(self, body_bytes):
124
        """Process the current search state and perform the parent lookup.
125
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
126
        :return: A smart server response where the body contains an utf8
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
127
            encoded flattened list of the parents of the revisions (the same
3211.5.3 by Robert Collins
Adjust size of batch and change gzip comments to bzip2.
128
            format as Repository.get_revision_graph) which has been bz2
129
            compressed.
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
130
        """
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
131
        repository = self._repository
132
        repository.lock_read()
133
        try:
134
            return self._do_repository_request(body_bytes)
135
        finally:
136
            repository.unlock()
137
138
    def _do_repository_request(self, body_bytes):
139
        repository = self._repository
140
        revision_ids = set(self._revision_ids)
141
        search, error = self.recreate_search(repository, body_bytes)
142
        if error is not None:
143
            return error
144
        # TODO might be nice to start up the search again; but thats not
145
        # written or tested yet.
146
        client_seen_revs = set(search.get_result().get_keys())
147
        # Always include the requested ids.
148
        client_seen_revs.difference_update(revision_ids)
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
149
        lines = []
150
        repo_graph = repository.get_graph()
151
        result = {}
152
        queried_revs = set()
153
        size_so_far = 0
154
        next_revs = revision_ids
155
        first_loop_done = False
156
        while next_revs:
157
            queried_revs.update(next_revs)
158
            parent_map = repo_graph.get_parent_map(next_revs)
159
            next_revs = set()
160
            for revision_id, parents in parent_map.iteritems():
161
                # adjust for the wire
162
                if parents == (_mod_revision.NULL_REVISION,):
163
                    parents = ()
164
                # prepare the next query
165
                next_revs.update(parents)
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
166
                if revision_id not in client_seen_revs:
167
                    # Client does not have this revision, give it to it.
168
                    # add parents to the result
169
                    result[revision_id] = parents
170
                    # Approximate the serialized cost of this revision_id.
171
                    size_so_far += 2 + len(revision_id) + sum(map(len, parents))
172
            # get all the directly asked for parents, and then flesh out to
173
            # 64K (compressed) or so. We do one level of depth at a time to
3211.5.3 by Robert Collins
Adjust size of batch and change gzip comments to bzip2.
174
            # stay in sync with the client. The 250000 magic number is
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
175
            # estimated compression ratio taken from bzr.dev itself.
3211.5.3 by Robert Collins
Adjust size of batch and change gzip comments to bzip2.
176
            if first_loop_done and size_so_far > 250000:
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
177
                next_revs = set()
178
                break
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
179
            # don't query things we've already queried
180
            next_revs.difference_update(queried_revs)
181
            first_loop_done = True
182
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
183
        # sorting trivially puts lexographically similar revision ids together.
184
        # Compression FTW.
185
        for revision, parents in sorted(result.items()):
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
186
            lines.append(' '.join((revision, ) + tuple(parents)))
187
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
188
        return SuccessfulSmartServerResponse(
3211.5.2 by Robert Collins
Change RemoteRepository.get_parent_map to use bz2 not gzip for compression.
189
            ('ok', ), bz2.compress('\n'.join(lines)))
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
190
191
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
192
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryReadLocked):
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
193
    
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
194
    def do_readlocked_repository_request(self, repository, revision_id):
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
195
        """Return the result of repository.get_revision_graph(revision_id).
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
196
197
        Deprecated as of bzr 1.4, but supported for older clients.
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
198
        
199
        :param repository: The repository to query in.
200
        :param revision_id: The utf8 encoded revision_id to get a graph from.
201
        :return: A smart server response where the body contains an utf8
202
            encoded flattened list of the revision graph.
203
        """
2018.5.83 by Andrew Bennetts
Fix some test failures caused by the switch from unicode to UTF-8-encoded strs for revision IDs.
204
        if not revision_id:
205
            revision_id = None
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
206
207
        lines = []
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
208
        graph = repository.get_graph()
209
        if revision_id:
210
            search_ids = [revision_id]
211
        else:
212
            search_ids = repository.all_revision_ids()
213
        search = graph._make_breadth_first_searcher(search_ids)
214
        transitive_ids = set()
215
        map(transitive_ids.update, list(search))
216
        parent_map = graph.get_parent_map(transitive_ids)
3287.6.8 by Robert Collins
Reduce code duplication as per review.
217
        revision_graph = _strip_NULL_ghosts(parent_map)
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
218
        if revision_id and revision_id not in revision_graph:
2018.14.1 by Andrew Bennetts
Update to current hpss branch? Fix lots of test failures.
219
            # Note that we return an empty body, rather than omitting the body.
220
            # This way the client knows that it can always expect to find a body
221
            # in the response for this method, even in the error case.
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
222
            return FailedSmartServerResponse(('nosuchrevision', revision_id), '')
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
223
224
        for revision, parents in revision_graph.items():
2592.3.50 by Robert Collins
Merge bzr.dev.
225
            lines.append(' '.join((revision, ) + tuple(parents)))
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
226
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
227
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
228
229
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
230
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
231
232
    def do_repository_request(self, repository, revision_id):
233
        """Return ok if a specific revision is in the repository at path.
234
235
        :param repository: The repository to query in.
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
236
        :param revision_id: The utf8 encoded revision_id to lookup.
237
        :return: A smart server response of ('ok', ) if the revision is
238
            present.
239
        """
2018.5.83 by Andrew Bennetts
Fix some test failures caused by the switch from unicode to UTF-8-encoded strs for revision IDs.
240
        if repository.has_revision(revision_id):
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
241
            return SuccessfulSmartServerResponse(('yes', ))
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
242
        else:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
243
            return SuccessfulSmartServerResponse(('no', ))
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
244
245
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
246
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
247
248
    def do_repository_request(self, repository, revid, committers):
249
        """Return the result of repository.gather_stats().
250
251
        :param repository: The repository to query in.
252
        :param revid: utf8 encoded rev id or an empty string to indicate None
253
        :param committers: 'yes' or 'no'.
254
255
        :return: A SmartServerResponse ('ok',), a encoded body looking like
256
              committers: 1
257
              firstrev: 1234.230 0
258
              latestrev: 345.700 3600
259
              revisions: 2
260
              size:45
261
262
              But containing only fields returned by the gather_stats() call
263
        """
264
        if revid == '':
265
            decoded_revision_id = None
266
        else:
2018.5.83 by Andrew Bennetts
Fix some test failures caused by the switch from unicode to UTF-8-encoded strs for revision IDs.
267
            decoded_revision_id = revid
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
268
        if committers == 'yes':
269
            decoded_committers = True
270
        else:
271
            decoded_committers = None
272
        stats = repository.gather_stats(decoded_revision_id, decoded_committers)
273
274
        body = ''
275
        if stats.has_key('committers'):
276
            body += 'committers: %d\n' % stats['committers']
277
        if stats.has_key('firstrev'):
278
            body += 'firstrev: %.3f %d\n' % stats['firstrev']
279
        if stats.has_key('latestrev'):
280
             body += 'latestrev: %.3f %d\n' % stats['latestrev']
281
        if stats.has_key('revisions'):
282
            body += 'revisions: %d\n' % stats['revisions']
283
        if stats.has_key('size'):
284
            body += 'size: %d\n' % stats['size']
285
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
286
        return SuccessfulSmartServerResponse(('ok', ), body)
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
287
288
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
289
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
290
291
    def do_repository_request(self, repository):
292
        """Return the result of repository.is_shared().
293
294
        :param repository: The repository to query in.
295
        :return: A smart server response of ('yes', ) if the repository is
296
            shared, and ('no', ) if it is not.
297
        """
298
        if repository.is_shared():
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
299
            return SuccessfulSmartServerResponse(('yes', ))
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
300
        else:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
301
            return SuccessfulSmartServerResponse(('no', ))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
302
303
304
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
305
2018.5.79 by Andrew Bennetts
Implement RemoteBranch.lock_write/unlock as smart operations.
306
    def do_repository_request(self, repository, token=''):
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
307
        # XXX: this probably should not have a token.
308
        if token == '':
309
            token = None
310
        try:
311
            token = repository.lock_write(token=token)
312
        except errors.LockContention, e:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
313
            return FailedSmartServerResponse(('LockContention',))
2018.5.95 by Andrew Bennetts
Add a Transport.is_readonly remote call, let {Branch,Repository}.lock_write remote call return UnlockableTransport, and miscellaneous test fixes.
314
        except errors.UnlockableTransport:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
315
            return FailedSmartServerResponse(('UnlockableTransport',))
2872.5.3 by Martin Pool
Pass back LockFailed from smart server lock methods
316
        except errors.LockFailed, e:
317
            return FailedSmartServerResponse(('LockFailed',
318
                str(e.lock), str(e.why)))
3015.2.7 by Robert Collins
In the RemoteServer repository methods handle repositories that cannot be remotely locked like pack repositories, and add a read lock in SmartServerRepositoryStreamKnitDataForRevisions.
319
        if token is not None:
320
            repository.leave_lock_in_place()
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
321
        repository.unlock()
322
        if token is None:
323
            token = ''
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
324
        return SuccessfulSmartServerResponse(('ok', token))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
325
326
327
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
328
329
    def do_repository_request(self, repository, token):
330
        try:
331
            repository.lock_write(token=token)
332
        except errors.TokenMismatch, e:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
333
            return FailedSmartServerResponse(('TokenMismatch',))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
334
        repository.dont_leave_lock_in_place()
335
        repository.unlock()
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
336
        return SuccessfulSmartServerResponse(('ok',))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
337
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
338
339
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
2018.18.11 by Martin Pool
merge hpss changes
340
    """Get the raw repository files as a tarball.
341
342
    The returned tarball contains a .bzr control directory which in turn
343
    contains a repository.
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
344
    
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
345
    This takes one parameter, compression, which currently must be 
346
    "", "gz", or "bz2".
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
347
348
    This is used to implement the Repository.copy_content_into operation.
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
349
    """
350
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
351
    def do_repository_request(self, repository, compression):
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
352
        from bzrlib import osutils
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
353
        repo_transport = repository.control_files._transport
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
354
        tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
2018.18.5 by Martin Pool
Repository.tarball locks repository while running for consistency
355
        try:
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
356
            controldir_name = tmp_dirname + '/.bzr'
357
            return self._tarfile_response(controldir_name, compression)
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
358
        finally:
359
            osutils.rmtree(tmp_dirname)
360
361
    def _copy_to_tempdir(self, from_repo):
362
        tmp_dirname = tempfile.mkdtemp(prefix='tmpbzrclone')
363
        tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
364
        tmp_repo = from_repo._format.initialize(tmp_bzrdir)
365
        from_repo.copy_content_into(tmp_repo)
366
        return tmp_dirname, tmp_repo
367
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
368
    def _tarfile_response(self, tmp_dirname, compression):
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
369
        temp = tempfile.NamedTemporaryFile()
370
        try:
2557.1.1 by Martin Pool
[BUG 119330] Fix tempfile permissions error in smart server tar bundling (under windows) (Martin_)
371
            self._tarball_of_dir(tmp_dirname, compression, temp.file)
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
372
            # all finished; write the tempfile out to the network
373
            temp.seek(0)
2420.2.2 by Andrew Bennetts
Merge tarball branch that's already with PQM, resolving conflicts.
374
            return SuccessfulSmartServerResponse(('ok',), temp.read())
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
375
            # FIXME: Don't read the whole thing into memory here; rather stream it
376
            # out from the file onto the network. mbp 20070411
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
377
        finally:
378
            temp.close()
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
379
2557.1.1 by Martin Pool
[BUG 119330] Fix tempfile permissions error in smart server tar bundling (under windows) (Martin_)
380
    def _tarball_of_dir(self, dirname, compression, ofile):
2571.2.2 by Ian Clatworthy
use basename as poolie recommended
381
        filename = os.path.basename(ofile.name)
382
        tarball = tarfile.open(fileobj=ofile, name=filename,
2571.2.1 by Ian Clatworthy
fix #123485 - selftest broken under Python 2.5.1 because of tafile API change
383
            mode='w|' + compression)
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
384
        try:
385
            # The tarball module only accepts ascii names, and (i guess)
386
            # packs them with their 8bit names.  We know all the files
387
            # within the repository have ASCII names so the should be safe
388
            # to pack in.
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
389
            dirname = dirname.encode(sys.getfilesystemencoding())
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
390
            # python's tarball module includes the whole path by default so
391
            # override it
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
392
            if not dirname.endswith('.bzr'):
393
                raise ValueError(dirname)
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
394
            tarball.add(dirname, '.bzr') # recursive by default
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
395
        finally:
396
            tarball.close()
2535.3.12 by Andrew Bennetts
Add a first cut of a get_data_stream method to Repository.
397
398
2535.3.49 by Andrew Bennetts
Rename 'Repository.fetch_revisions' smart request to 'Repository.stream_knit_data_for_revisions'.
399
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
3184.1.10 by Robert Collins
Change the smart server verb for Repository.stream_revisions_chunked to use SearchResults as the request mechanism for downloads.
400
    """Bzr <= 1.1 streaming pull, buffers all data on server."""
2535.3.14 by Andrew Bennetts
Move serialising repo data stream to bytes into smart protocol.
401
402
    def do_repository_request(self, repository, *revision_ids):
3015.2.7 by Robert Collins
In the RemoteServer repository methods handle repositories that cannot be remotely locked like pack repositories, and add a read lock in SmartServerRepositoryStreamKnitDataForRevisions.
403
        repository.lock_read()
404
        try:
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
405
            return self._do_repository_request(repository, revision_ids)
406
        finally:
407
            repository.unlock()
408
409
    def _do_repository_request(self, repository, revision_ids):
3184.1.9 by Robert Collins
* ``Repository.get_data_stream`` is now deprecated in favour of
410
        stream = repository.get_data_stream_for_search(
411
            repository.revision_ids_to_search_result(set(revision_ids)))
2535.4.30 by Andrew Bennetts
Use ContainerSerialiser rather than ContainerWriter in bzrlib/smart/repository.py.
412
        buffer = StringIO()
413
        pack = ContainerSerialiser()
414
        buffer.write(pack.begin())
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
415
        try:
3381.1.3 by Aaron Bentley
Stop locking in get_data_stream_for_search
416
            for name_tuple, bytes in stream:
417
                buffer.write(pack.bytes_record(bytes, [name_tuple]))
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
418
        except errors.RevisionNotPresent, e:
419
            return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
2535.4.30 by Andrew Bennetts
Use ContainerSerialiser rather than ContainerWriter in bzrlib/smart/repository.py.
420
        buffer.write(pack.end())
421
        return SuccessfulSmartServerResponse(('ok',), buffer.getvalue())
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
422
423
424
class SmartServerRepositoryStreamRevisionsChunked(SmartServerRepositoryRequest):
3184.1.10 by Robert Collins
Change the smart server verb for Repository.stream_revisions_chunked to use SearchResults as the request mechanism for downloads.
425
    """Bzr 1.1+ streaming pull."""
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
426
3184.1.10 by Robert Collins
Change the smart server verb for Repository.stream_revisions_chunked to use SearchResults as the request mechanism for downloads.
427
    def do_body(self, body_bytes):
428
        repository = self._repository
2535.4.29 by Andrew Bennetts
Add a new smart method, Repository.stream_revisions_chunked, rather than changing the behaviour of an existing method.
429
        repository.lock_read()
430
        try:
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
431
            search, error = self.recreate_search(repository, body_bytes)
432
            if error is not None:
3194.1.2 by Andrew Bennetts
Fix another LockableFiles not unlocked warning from test_smart.
433
                repository.unlock()
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
434
                return error
435
            stream = repository.get_data_stream_for_search(search.get_result())
2535.4.26 by Andrew Bennetts
Fix locking problem that was hanging the tests.
436
        except Exception:
3211.5.1 by Robert Collins
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.
437
            # On non-error, unlocking is done by the body stream handler.
3015.2.7 by Robert Collins
In the RemoteServer repository methods handle repositories that cannot be remotely locked like pack repositories, and add a read lock in SmartServerRepositoryStreamKnitDataForRevisions.
438
            repository.unlock()
2535.4.26 by Andrew Bennetts
Fix locking problem that was hanging the tests.
439
            raise
2535.4.2 by Andrew Bennetts
Nasty hackery to make stream_knit_data_for_revisions response use streaming.
440
        return SuccessfulSmartServerResponse(('ok',),
2535.4.26 by Andrew Bennetts
Fix locking problem that was hanging the tests.
441
            body_stream=self.body_stream(stream, repository))
2535.4.2 by Andrew Bennetts
Nasty hackery to make stream_knit_data_for_revisions response use streaming.
442
2535.4.26 by Andrew Bennetts
Fix locking problem that was hanging the tests.
443
    def body_stream(self, stream, repository):
2535.4.18 by Andrew Bennetts
Use pack.ContainerSerialiser to remove some nasty cruft.
444
        pack = ContainerSerialiser()
445
        yield pack.begin()
2535.3.40 by Andrew Bennetts
Tidy up more XXXs.
446
        try:
2692.1.23 by Andrew Bennetts
Fix LockWarnings from test_smart.
447
            try:
448
                for name_tuple, bytes in stream:
449
                    yield pack.bytes_record(bytes, [name_tuple])
450
            except:
451
                # Undo the lock_read that that happens once the iterator from
452
                # get_data_stream is started.
453
                repository.unlock()
454
                raise
2535.3.40 by Andrew Bennetts
Tidy up more XXXs.
455
        except errors.RevisionNotPresent, e:
3184.1.10 by Robert Collins
Change the smart server verb for Repository.stream_revisions_chunked to use SearchResults as the request mechanism for downloads.
456
            # This shouldn't be able to happen, but as we don't buffer
457
            # everything it can in theory happen.
2692.1.23 by Andrew Bennetts
Fix LockWarnings from test_smart.
458
            repository.unlock()
2535.4.5 by Andrew Bennetts
Merge latest chunking protocol, including support for errors, fixing a test failure.
459
            yield FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
2692.1.23 by Andrew Bennetts
Fix LockWarnings from test_smart.
460
        else:
461
            repository.unlock()
462
            pack.end()
2535.3.15 by Andrew Bennetts
Add KnitVersionedFile.get_stream_as_bytes, start smart implementation of RemoteRepository.get_data_stream.
463