/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
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
2571.2.2 by Ian Clatworthy
use basename as poolie recommended
20
import os
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
21
import Queue
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
22
import struct
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
23
import sys
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
24
import tarfile
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
25
import tempfile
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
26
import threading
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
27
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
28
from bzrlib import (
2694.5.4 by Jelmer Vernooij
Move bzrlib.util.bencode to bzrlib._bencode_py.
29
    bencode,
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
30
    errors,
4070.9.2 by Andrew Bennetts
Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.
31
    graph,
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
32
    osutils,
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
33
    pack,
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
34
    )
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
35
from bzrlib.bzrdir import BzrDir
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
36
from bzrlib.smart.request import (
37
    FailedSmartServerResponse,
38
    SmartServerRequest,
39
    SuccessfulSmartServerResponse,
40
    )
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
41
from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
42
from bzrlib import revision as _mod_revision
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
43
from bzrlib.versionedfile import NetworkRecordStream, record_to_fulltext_bytes
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
44
45
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
46
class SmartServerRepositoryRequest(SmartServerRequest):
47
    """Common base class for Repository requests."""
48
49
    def do(self, path, *args):
50
        """Execute a repository request.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
51
2692.1.10 by Andrew Bennetts
More docstring polish
52
        All Repository requests take a path to the repository as their first
53
        argument.  The repository must be at the exact path given by the
54
        client - no searching is done.
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
55
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
56
        The actual logic is delegated to self.do_repository_request.
57
2692.1.10 by Andrew Bennetts
More docstring polish
58
        :param client_path: The path for the repository as received from the
59
            client.
60
        :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).
61
        """
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
62
        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).
63
        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.
64
        # Save the repository for use with do_body.
65
        self._repository = bzrdir.open_repository()
66
        return self.do_repository_request(self._repository, *args)
67
68
    def do_repository_request(self, repository, *args):
69
        """Override to provide an implementation for a verb."""
70
        # No-op for verbs that take bodies (None as a result indicates a body
71
        # is expected)
72
        return None
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
73
4332.2.1 by Robert Collins
Fix bug 360791 by not raising an error when a smart server is asked for more content than it has locally; the client is assumed to be monitoring what it gets.
74
    def recreate_search(self, repository, search_bytes, discard_excess=False):
75
        """Recreate a search from its serialised form.
76
77
        :param discard_excess: If True, and the search refers to data we don't
78
            have, just silently accept that fact - the verb calling
79
            recreate_search trusts that clients will look for missing things
80
            they expected and get it from elsewhere.
81
        """
4070.9.5 by Andrew Bennetts
Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.
82
        lines = search_bytes.split('\n')
83
        if lines[0] == 'ancestry-of':
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
84
            heads = lines[1:]
85
            search_result = graph.PendingAncestryResult(heads, repository)
4070.9.5 by Andrew Bennetts
Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.
86
            return search_result, None
87
        elif lines[0] == 'search':
4332.2.1 by Robert Collins
Fix bug 360791 by not raising an error when a smart server is asked for more content than it has locally; the client is assumed to be monitoring what it gets.
88
            return self.recreate_search_from_recipe(repository, lines[1:],
89
                discard_excess=discard_excess)
4070.9.5 by Andrew Bennetts
Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.
90
        else:
91
            return (None, FailedSmartServerResponse(('BadSearch',)))
92
4332.2.1 by Robert Collins
Fix bug 360791 by not raising an error when a smart server is asked for more content than it has locally; the client is assumed to be monitoring what it gets.
93
    def recreate_search_from_recipe(self, repository, lines,
94
        discard_excess=False):
95
        """Recreate a specific revision search (vs a from-tip search).
96
97
        :param discard_excess: If True, and the search refers to data we don't
98
            have, just silently accept that fact - the verb calling
99
            recreate_search trusts that clients will look for missing things
100
            they expected and get it from elsewhere.
101
        """
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.
102
        start_keys = set(lines[0].split(' '))
103
        exclude_keys = set(lines[1].split(' '))
104
        revision_count = int(lines[2])
105
        repository.lock_read()
106
        try:
107
            search = repository.get_graph()._make_breadth_first_searcher(
108
                start_keys)
109
            while True:
110
                try:
111
                    next_revs = search.next()
112
                except StopIteration:
113
                    break
114
                search.stop_searching_any(exclude_keys.intersection(next_revs))
115
            search_result = search.get_result()
4332.2.1 by Robert Collins
Fix bug 360791 by not raising an error when a smart server is asked for more content than it has locally; the client is assumed to be monitoring what it gets.
116
            if (not discard_excess and
117
                search_result.get_recipe()[3] != revision_count):
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.
118
                # we got back a different amount of data than expected, this
119
                # gets reported as NoSuchRevision, because less revisions
120
                # indicates missing revisions, and more should never happen as
121
                # the excludes list considers ghosts and ensures that ghost
122
                # filling races are not a problem.
123
                return (None, FailedSmartServerResponse(('NoSuchRevision',)))
4070.9.2 by Andrew Bennetts
Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.
124
            return (search_result, None)
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.
125
        finally:
126
            repository.unlock()
127
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
128
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
129
class SmartServerRepositoryReadLocked(SmartServerRepositoryRequest):
130
    """Calls self.do_readlocked_repository_request."""
131
132
    def do_repository_request(self, repository, *args):
133
        """Read lock a repository for do_readlocked_repository_request."""
134
        repository.lock_read()
135
        try:
136
            return self.do_readlocked_repository_request(repository, *args)
137
        finally:
138
            repository.unlock()
139
140
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
141
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.
142
    """Bzr 1.2+ - get parent data for revisions during a graph search."""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
143
4035.2.1 by Andrew Bennetts
Fix unnecessary get_parent_map calls after insert_stream during push.
144
    no_extra_results = False
145
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
146
    def do_repository_request(self, repository, *revision_ids):
147
        """Get parent details for some revisions.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
148
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
149
        All the parents for revision_ids are returned. Additionally up to 64KB
150
        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.
151
        from revision_ids is returned. The verb takes a body containing the
152
        current search state, see do_body for details.
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
153
4190.1.5 by Robert Collins
Review tweaks.
154
        If 'include-missing:' is in revision_ids, ghosts encountered in the
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
155
        graph traversal for getting parent data are included in the result with
156
        a prefix of 'missing:'.
157
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
158
        :param repository: The repository to query in.
3172.5.8 by Robert Collins
Review feedback.
159
        :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.
160
        """
161
        self._revision_ids = revision_ids
162
        return None # Signal that we want a body.
163
164
    def do_body(self, body_bytes):
165
        """Process the current search state and perform the parent lookup.
166
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
167
        :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.
168
            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.
169
            format as Repository.get_revision_graph) which has been bz2
170
            compressed.
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
171
        """
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.
172
        repository = self._repository
173
        repository.lock_read()
174
        try:
175
            return self._do_repository_request(body_bytes)
176
        finally:
177
            repository.unlock()
178
179
    def _do_repository_request(self, body_bytes):
180
        repository = self._repository
181
        revision_ids = set(self._revision_ids)
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
182
        include_missing = 'include-missing:' in revision_ids
183
        if include_missing:
184
            revision_ids.remove('include-missing:')
4070.9.5 by Andrew Bennetts
Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.
185
        body_lines = body_bytes.split('\n')
186
        search_result, error = self.recreate_search_from_recipe(
187
            repository, body_lines)
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
        if error is not None:
189
            return error
190
        # TODO might be nice to start up the search again; but thats not
191
        # written or tested yet.
4070.9.2 by Andrew Bennetts
Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.
192
        client_seen_revs = set(search_result.get_keys())
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.
193
        # Always include the requested ids.
194
        client_seen_revs.difference_update(revision_ids)
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
195
        lines = []
196
        repo_graph = repository.get_graph()
197
        result = {}
198
        queried_revs = set()
199
        size_so_far = 0
200
        next_revs = revision_ids
201
        first_loop_done = False
202
        while next_revs:
203
            queried_revs.update(next_revs)
204
            parent_map = repo_graph.get_parent_map(next_revs)
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
205
            current_revs = next_revs
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
206
            next_revs = set()
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
207
            for revision_id in current_revs:
208
                missing_rev = False
209
                parents = parent_map.get(revision_id)
210
                if parents is not None:
211
                    # adjust for the wire
212
                    if parents == (_mod_revision.NULL_REVISION,):
213
                        parents = ()
214
                    # prepare the next query
215
                    next_revs.update(parents)
216
                    encoded_id = revision_id
217
                else:
218
                    missing_rev = True
219
                    encoded_id = "missing:" + revision_id
220
                    parents = []
221
                if (revision_id not in client_seen_revs and
222
                    (not missing_rev or include_missing)):
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.
223
                    # Client does not have this revision, give it to it.
224
                    # add parents to the result
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
225
                    result[encoded_id] = 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.
226
                    # Approximate the serialized cost of this revision_id.
4190.1.3 by Robert Collins
Allow optional inclusion of ghost data in server get_parent_map calls.
227
                    size_so_far += 2 + len(encoded_id) + sum(map(len, 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.
228
            # get all the directly asked for parents, and then flesh out to
229
            # 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.
230
            # 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.
231
            # estimated compression ratio taken from bzr.dev itself.
4035.2.1 by Andrew Bennetts
Fix unnecessary get_parent_map calls after insert_stream during push.
232
            if self.no_extra_results or (
233
                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.
234
                next_revs = set()
235
                break
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
236
            # don't query things we've already queried
237
            next_revs.difference_update(queried_revs)
238
            first_loop_done = True
239
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.
240
        # sorting trivially puts lexographically similar revision ids together.
241
        # Compression FTW.
242
        for revision, parents in sorted(result.items()):
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
243
            lines.append(' '.join((revision, ) + tuple(parents)))
244
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.
245
        return SuccessfulSmartServerResponse(
3211.5.2 by Robert Collins
Change RemoteRepository.get_parent_map to use bz2 not gzip for compression.
246
            ('ok', ), bz2.compress('\n'.join(lines)))
3172.5.6 by Robert Collins
Create new smart server verb Repository.get_parent_map.
247
248
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
249
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryReadLocked):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
250
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
251
    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)
252
        """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.
253
254
        Deprecated as of bzr 1.4, but supported for older clients.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
255
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
256
        :param repository: The repository to query in.
257
        :param revision_id: The utf8 encoded revision_id to get a graph from.
258
        :return: A smart server response where the body contains an utf8
259
            encoded flattened list of the revision graph.
260
        """
2018.5.83 by Andrew Bennetts
Fix some test failures caused by the switch from unicode to UTF-8-encoded strs for revision IDs.
261
        if not revision_id:
262
            revision_id = None
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
263
264
        lines = []
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
265
        graph = repository.get_graph()
266
        if revision_id:
267
            search_ids = [revision_id]
268
        else:
269
            search_ids = repository.all_revision_ids()
270
        search = graph._make_breadth_first_searcher(search_ids)
271
        transitive_ids = set()
272
        map(transitive_ids.update, list(search))
273
        parent_map = graph.get_parent_map(transitive_ids)
3287.6.8 by Robert Collins
Reduce code duplication as per review.
274
        revision_graph = _strip_NULL_ghosts(parent_map)
3287.6.1 by Robert Collins
* ``VersionedFile.get_graph`` is deprecated, with no replacement method.
275
        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.
276
            # Note that we return an empty body, rather than omitting the body.
277
            # This way the client knows that it can always expect to find a body
278
            # 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.
279
            return FailedSmartServerResponse(('nosuchrevision', revision_id), '')
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
280
281
        for revision, parents in revision_graph.items():
2592.3.50 by Robert Collins
Merge bzr.dev.
282
            lines.append(' '.join((revision, ) + tuple(parents)))
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
283
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
284
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
2018.5.67 by Wouter van Heyst
Implement RemoteRepository.get_revision_graph (Wouter van Heyst, Robert Collins)
285
286
2018.5.56 by Robert Collins
Factor out code we expect to be common in SmartServerRequestHasRevision to SmartServerRepositoryRequest (Robert Collins, Vincent Ladeuil).
287
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
288
289
    def do_repository_request(self, repository, revision_id):
290
        """Return ok if a specific revision is in the repository at path.
291
292
        :param repository: The repository to query in.
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
293
        :param revision_id: The utf8 encoded revision_id to lookup.
294
        :return: A smart server response of ('ok', ) if the revision is
295
            present.
296
        """
2018.5.83 by Andrew Bennetts
Fix some test failures caused by the switch from unicode to UTF-8-encoded strs for revision IDs.
297
        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.
298
            return SuccessfulSmartServerResponse(('yes', ))
2018.5.40 by Robert Collins
Implement a remote Repository.has_revision method.
299
        else:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
300
            return SuccessfulSmartServerResponse(('no', ))
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
301
302
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
303
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
304
305
    def do_repository_request(self, repository, revid, committers):
306
        """Return the result of repository.gather_stats().
307
308
        :param repository: The repository to query in.
309
        :param revid: utf8 encoded rev id or an empty string to indicate None
310
        :param committers: 'yes' or 'no'.
311
312
        :return: A SmartServerResponse ('ok',), a encoded body looking like
313
              committers: 1
314
              firstrev: 1234.230 0
315
              latestrev: 345.700 3600
316
              revisions: 2
317
318
              But containing only fields returned by the gather_stats() call
319
        """
320
        if revid == '':
321
            decoded_revision_id = None
322
        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.
323
            decoded_revision_id = revid
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
324
        if committers == 'yes':
325
            decoded_committers = True
326
        else:
327
            decoded_committers = None
328
        stats = repository.gather_stats(decoded_revision_id, decoded_committers)
329
330
        body = ''
331
        if stats.has_key('committers'):
332
            body += 'committers: %d\n' % stats['committers']
333
        if stats.has_key('firstrev'):
334
            body += 'firstrev: %.3f %d\n' % stats['firstrev']
335
        if stats.has_key('latestrev'):
336
             body += 'latestrev: %.3f %d\n' % stats['latestrev']
337
        if stats.has_key('revisions'):
338
            body += 'revisions: %d\n' % stats['revisions']
339
        if stats.has_key('size'):
340
            body += 'size: %d\n' % stats['size']
341
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
342
        return SuccessfulSmartServerResponse(('ok', ), body)
2018.10.2 by v.ladeuil+lp at free
gather_stats server side and request registration
343
344
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
345
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
346
347
    def do_repository_request(self, repository):
348
        """Return the result of repository.is_shared().
349
350
        :param repository: The repository to query in.
351
        :return: A smart server response of ('yes', ) if the repository is
352
            shared, and ('no', ) if it is not.
353
        """
354
        if repository.is_shared():
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
355
            return SuccessfulSmartServerResponse(('yes', ))
2018.5.57 by Robert Collins
Implement RemoteRepository.is_shared (Robert Collins, Vincent Ladeuil).
356
        else:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
357
            return SuccessfulSmartServerResponse(('no', ))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
358
359
360
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
361
2018.5.79 by Andrew Bennetts
Implement RemoteBranch.lock_write/unlock as smart operations.
362
    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
363
        # XXX: this probably should not have a token.
364
        if token == '':
365
            token = None
366
        try:
367
            token = repository.lock_write(token=token)
368
        except errors.LockContention, e:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
369
            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.
370
        except errors.UnlockableTransport:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
371
            return FailedSmartServerResponse(('UnlockableTransport',))
2872.5.3 by Martin Pool
Pass back LockFailed from smart server lock methods
372
        except errors.LockFailed, e:
373
            return FailedSmartServerResponse(('LockFailed',
374
                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.
375
        if token is not None:
376
            repository.leave_lock_in_place()
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
377
        repository.unlock()
378
        if token is None:
379
            token = ''
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
380
        return SuccessfulSmartServerResponse(('ok', token))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
381
382
4060.1.5 by Robert Collins
Verb change name requested by Andrew.
383
class SmartServerRepositoryGetStream(SmartServerRepositoryRequest):
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
384
385
    def do_repository_request(self, repository, to_network_name):
386
        """Get a stream for inserting into a to_format repository.
387
388
        :param repository: The repository to stream from.
389
        :param to_network_name: The network name of the format of the target
390
            repository.
391
        """
392
        self._to_format = network_format_registry.get(to_network_name)
393
        return None # Signal that we want a body.
394
395
    def do_body(self, body_bytes):
396
        repository = self._repository
397
        repository.lock_read()
398
        try:
4332.2.1 by Robert Collins
Fix bug 360791 by not raising an error when a smart server is asked for more content than it has locally; the client is assumed to be monitoring what it gets.
399
            search_result, error = self.recreate_search(repository, body_bytes,
400
                discard_excess=True)
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
401
            if error is not None:
402
                repository.unlock()
403
                return error
404
            source = repository._get_source(self._to_format)
4070.9.2 by Andrew Bennetts
Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.
405
            stream = source.get_stream(search_result)
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
406
        except Exception:
407
            exc_info = sys.exc_info()
408
            try:
409
                # On non-error, unlocking is done by the body stream handler.
410
                repository.unlock()
411
            finally:
412
                raise exc_info[0], exc_info[1], exc_info[2]
413
        return SuccessfulSmartServerResponse(('ok',),
414
            body_stream=self.body_stream(stream, repository))
415
416
    def body_stream(self, stream, repository):
417
        byte_stream = _stream_to_byte_stream(stream, repository._format)
418
        try:
419
            for bytes in byte_stream:
420
                yield bytes
421
        except errors.RevisionNotPresent, e:
422
            # This shouldn't be able to happen, but as we don't buffer
423
            # everything it can in theory happen.
424
            repository.unlock()
425
            yield FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
426
        else:
427
            repository.unlock()
428
429
430
def _stream_to_byte_stream(stream, src_format):
431
    """Convert a record stream to a self delimited byte stream."""
432
    pack_writer = pack.ContainerSerialiser()
433
    yield pack_writer.begin()
434
    yield pack_writer.bytes_record(src_format.network_name(), '')
435
    for substream_type, substream in stream:
436
        for record in substream:
437
            if record.storage_kind in ('chunked', 'fulltext'):
438
                serialised = record_to_fulltext_bytes(record)
4392.2.2 by John Arbash Meinel
Add tests that ensure we can fetch branches with ghosts in their ancestry.
439
            elif record.storage_kind == 'absent':
440
                raise ValueError("Absent factory for %s" % (record.key,))
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
441
            else:
442
                serialised = record.get_bytes_as(record.storage_kind)
443
            if serialised:
444
                # Some streams embed the whole stream into the wire
445
                # representation of the first record, which means that
446
                # later records have no wire representation: we skip them.
447
                yield pack_writer.bytes_record(serialised, [(substream_type,)])
448
    yield pack_writer.end()
449
450
451
def _byte_stream_to_stream(byte_stream):
4060.1.5 by Robert Collins
Verb change name requested by Andrew.
452
    """Convert a byte stream into a format and a stream.
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
453
454
    :param byte_stream: A bytes iterator, as output by _stream_to_byte_stream.
455
    :return: (RepositoryFormat, stream_generator)
456
    """
457
    stream_decoder = pack.ContainerPushParser()
458
    def record_stream():
459
        """Closure to return the substreams."""
460
        # May have fully parsed records already.
461
        for record in stream_decoder.read_pending_records():
462
            record_names, record_bytes = record
463
            record_name, = record_names
464
            substream_type = record_name[0]
465
            substream = NetworkRecordStream([record_bytes])
466
            yield substream_type, substream.read()
467
        for bytes in byte_stream:
468
            stream_decoder.accept_bytes(bytes)
469
            for record in stream_decoder.read_pending_records():
470
                record_names, record_bytes = record
471
                record_name, = record_names
472
                substream_type = record_name[0]
473
                substream = NetworkRecordStream([record_bytes])
474
                yield substream_type, substream.read()
475
    for bytes in byte_stream:
476
        stream_decoder.accept_bytes(bytes)
477
        for record in stream_decoder.read_pending_records(max=1):
478
            record_names, src_format_name = record
479
            src_format = network_format_registry.get(src_format_name)
480
            return src_format, record_stream()
481
482
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
483
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
484
485
    def do_repository_request(self, repository, token):
486
        try:
487
            repository.lock_write(token=token)
488
        except errors.TokenMismatch, e:
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
489
            return FailedSmartServerResponse(('TokenMismatch',))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
490
        repository.dont_leave_lock_in_place()
491
        repository.unlock()
2432.4.5 by Robert Collins
Make using SuccessfulSmartServerResponse and FailedSmartServerResponse mandatory rather than optional in smart server logic.
492
        return SuccessfulSmartServerResponse(('ok',))
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
493
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
494
4017.3.4 by Robert Collins
Create a verb for Repository.set_make_working_trees.
495
class SmartServerRepositorySetMakeWorkingTrees(SmartServerRepositoryRequest):
496
497
    def do_repository_request(self, repository, str_bool_new_value):
498
        if str_bool_new_value == 'True':
499
            new_value = True
500
        else:
501
            new_value = False
502
        repository.set_make_working_trees(new_value)
503
        return SuccessfulSmartServerResponse(('ok',))
504
505
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
506
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
2018.18.11 by Martin Pool
merge hpss changes
507
    """Get the raw repository files as a tarball.
508
509
    The returned tarball contains a .bzr control directory which in turn
510
    contains a repository.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
511
512
    This takes one parameter, compression, which currently must be
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
513
    "", "gz", or "bz2".
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
514
515
    This is used to implement the Repository.copy_content_into operation.
2018.18.1 by Martin Pool
Add stub Repository.tarball smart method
516
    """
517
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
518
    def do_repository_request(self, repository, compression):
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
519
        tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
2018.18.5 by Martin Pool
Repository.tarball locks repository while running for consistency
520
        try:
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
521
            controldir_name = tmp_dirname + '/.bzr'
522
            return self._tarfile_response(controldir_name, compression)
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
523
        finally:
524
            osutils.rmtree(tmp_dirname)
525
526
    def _copy_to_tempdir(self, from_repo):
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
527
        tmp_dirname = osutils.mkdtemp(prefix='tmpbzrclone')
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
528
        tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
529
        tmp_repo = from_repo._format.initialize(tmp_bzrdir)
530
        from_repo.copy_content_into(tmp_repo)
531
        return tmp_dirname, tmp_repo
532
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
533
    def _tarfile_response(self, tmp_dirname, compression):
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
534
        temp = tempfile.NamedTemporaryFile()
535
        try:
2557.1.1 by Martin Pool
[BUG 119330] Fix tempfile permissions error in smart server tar bundling (under windows) (Martin_)
536
            self._tarball_of_dir(tmp_dirname, compression, temp.file)
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
537
            # all finished; write the tempfile out to the network
538
            temp.seek(0)
2420.2.2 by Andrew Bennetts
Merge tarball branch that's already with PQM, resolving conflicts.
539
            return SuccessfulSmartServerResponse(('ok',), temp.read())
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
540
            # FIXME: Don't read the whole thing into memory here; rather stream
541
            # it out from the file onto the network. mbp 20070411
2018.18.2 by Martin Pool
smart method Repository.tarball actually returns the tarball
542
        finally:
543
            temp.close()
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
544
2557.1.1 by Martin Pool
[BUG 119330] Fix tempfile permissions error in smart server tar bundling (under windows) (Martin_)
545
    def _tarball_of_dir(self, dirname, compression, ofile):
2571.2.2 by Ian Clatworthy
use basename as poolie recommended
546
        filename = os.path.basename(ofile.name)
547
        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
548
            mode='w|' + compression)
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
549
        try:
550
            # The tarball module only accepts ascii names, and (i guess)
551
            # packs them with their 8bit names.  We know all the files
552
            # within the repository have ASCII names so the should be safe
553
            # to pack in.
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
554
            dirname = dirname.encode(sys.getfilesystemencoding())
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
555
            # python's tarball module includes the whole path by default so
556
            # override it
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
557
            if not dirname.endswith('.bzr'):
558
                raise ValueError(dirname)
2018.18.10 by Martin Pool
copy_content_into from Remote repositories by using temporary directories on both ends.
559
            tarball.add(dirname, '.bzr') # recursive by default
2018.18.9 by Martin Pool
remote Repository.tarball builds a temporary directory and tars that
560
        finally:
561
            tarball.close()
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
562
563
4144.3.1 by Andrew Bennetts
Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
564
class SmartServerRepositoryInsertStreamLocked(SmartServerRepositoryRequest):
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
565
    """Insert a record stream from a RemoteSink into a repository.
566
567
    This gets bytes pushed to it by the network infrastructure and turns that
568
    into a bytes iterator using a thread. That is then processed by
569
    _byte_stream_to_stream.
4144.3.1 by Andrew Bennetts
Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
570
571
    New in 1.14.
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
572
    """
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
573
4144.3.1 by Andrew Bennetts
Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
574
    def do_repository_request(self, repository, resume_tokens, lock_token):
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
575
        """StreamSink.insert_stream for a remote repository."""
4144.3.1 by Andrew Bennetts
Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
576
        repository.lock_write(token=lock_token)
577
        self.do_insert_stream_request(repository, resume_tokens)
578
579
    def do_insert_stream_request(self, repository, resume_tokens):
4029.2.1 by Robert Collins
Support streaming push to stacked branches.
580
        tokens = [token for token in resume_tokens.split(' ') if token]
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
581
        self.tokens = tokens
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
582
        self.repository = repository
583
        self.queue = Queue.Queue()
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
584
        self.insert_thread = threading.Thread(target=self._inserter_thread)
585
        self.insert_thread.start()
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
586
587
    def do_chunk(self, body_stream_chunk):
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
588
        self.queue.put(body_stream_chunk)
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
589
590
    def _inserter_thread(self):
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
591
        try:
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
592
            src_format, stream = _byte_stream_to_stream(
593
                self.blocking_byte_stream())
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
594
            self.insert_result = self.repository._get_sink().insert_stream(
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
595
                stream, src_format, self.tokens)
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
596
            self.insert_ok = True
597
        except:
598
            self.insert_exception = sys.exc_info()
599
            self.insert_ok = False
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
600
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
601
    def blocking_byte_stream(self):
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
602
        while True:
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
603
            bytes = self.queue.get()
604
            if bytes is StopIteration:
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
605
                return
606
            else:
4060.1.4 by Robert Collins
Streaming fetch from remote servers.
607
                yield bytes
4022.1.6 by Robert Collins
Cherrypick and polish the RemoteSink for streaming push.
608
609
    def do_end(self):
610
        self.queue.put(StopIteration)
611
        if self.insert_thread is not None:
612
            self.insert_thread.join()
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
613
        if not self.insert_ok:
614
            exc_info = self.insert_exception
615
            raise exc_info[0], exc_info[1], exc_info[2]
616
        write_group_tokens, missing_keys = self.insert_result
617
        if write_group_tokens or missing_keys:
618
            # bzip needed? missing keys should typically be a small set.
619
            # Should this be a streaming body response ?
620
            missing_keys = sorted(missing_keys)
621
            bytes = bencode.bencode((write_group_tokens, missing_keys))
622
            self.repository.unlock()
623
            return SuccessfulSmartServerResponse(('missing-basis', bytes))
4029.2.1 by Robert Collins
Support streaming push to stacked branches.
624
        else:
4032.3.7 by Robert Collins
Move write locking and write group responsibilities into the Sink objects themselves, allowing complete avoidance of unnecessary calls when the sink is a RemoteSink.
625
            self.repository.unlock()
626
            return SuccessfulSmartServerResponse(('ok', ))
4144.3.1 by Andrew Bennetts
Add Repository.insert_stream_locked server-side implementation, plus tests for server-side _translate_error.
627
628
629
class SmartServerRepositoryInsertStream(SmartServerRepositoryInsertStreamLocked):
630
    """Insert a record stream from a RemoteSink into an unlocked repository.
631
632
    This is the same as SmartServerRepositoryInsertStreamLocked, except it
633
    takes no lock_tokens; i.e. it works with an unlocked (or lock-free, e.g.
634
    like pack format) repository.
635
636
    New in 1.13.
637
    """
638
639
    def do_repository_request(self, repository, resume_tokens):
640
        """StreamSink.insert_stream for a remote repository."""
641
        repository.lock_write()
642
        self.do_insert_stream_request(repository, resume_tokens)
643
644