~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Martin Packman
  • Date: 2012-01-05 10:37:58 UTC
  • mto: This revision was merged to the branch mainline in revision 6427.
  • Revision ID: martin.packman@canonical.com-20120105103758-wzftnmsip5iv9n2g
Revert addition of get_message_encoding function

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Server-side repository related request implmentations."""
 
17
"""Server-side repository related request implementations."""
18
18
 
19
19
import bz2
20
20
import os
22
22
import sys
23
23
import tempfile
24
24
import threading
 
25
import zlib
25
26
 
26
27
from bzrlib import (
27
28
    bencode,
28
29
    errors,
29
 
    graph,
 
30
    estimate_compressed_size,
 
31
    inventory as _mod_inventory,
 
32
    inventory_delta,
30
33
    osutils,
31
34
    pack,
 
35
    trace,
32
36
    ui,
 
37
    vf_search,
33
38
    )
34
39
from bzrlib.bzrdir import BzrDir
35
40
from bzrlib.smart.request import (
40
45
from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
41
46
from bzrlib import revision as _mod_revision
42
47
from bzrlib.versionedfile import (
 
48
    ChunkedContentFactory,
43
49
    NetworkRecordStream,
44
50
    record_to_fulltext_bytes,
45
51
    )
82
88
            they expected and get it from elsewhere.
83
89
        """
84
90
        if search_bytes == 'everything':
85
 
            return graph.EverythingResult(repository), None
 
91
            return vf_search.EverythingResult(repository), None
86
92
        lines = search_bytes.split('\n')
87
93
        if lines[0] == 'ancestry-of':
88
94
            heads = lines[1:]
89
 
            search_result = graph.PendingAncestryResult(heads, repository)
 
95
            search_result = vf_search.PendingAncestryResult(heads, repository)
90
96
            return search_result, None
91
97
        elif lines[0] == 'search':
92
98
            return self.recreate_search_from_recipe(repository, lines[1:],
116
122
                except StopIteration:
117
123
                    break
118
124
                search.stop_searching_any(exclude_keys.intersection(next_revs))
119
 
            search_result = search.get_result()
120
 
            if (not discard_excess and
121
 
                search_result.get_recipe()[3] != revision_count):
 
125
            (started_keys, excludes, included_keys) = search.get_state()
 
126
            if (not discard_excess and len(included_keys) != revision_count):
122
127
                # we got back a different amount of data than expected, this
123
128
                # gets reported as NoSuchRevision, because less revisions
124
129
                # indicates missing revisions, and more should never happen as
125
130
                # the excludes list considers ghosts and ensures that ghost
126
131
                # filling races are not a problem.
127
132
                return (None, FailedSmartServerResponse(('NoSuchRevision',)))
 
133
            search_result = vf_search.SearchResult(started_keys, excludes,
 
134
                len(included_keys), included_keys)
128
135
            return (search_result, None)
129
136
        finally:
130
137
            repository.unlock()
142
149
            repository.unlock()
143
150
 
144
151
 
 
152
class SmartServerRepositoryBreakLock(SmartServerRepositoryRequest):
 
153
    """Break a repository lock."""
 
154
 
 
155
    def do_repository_request(self, repository):
 
156
        repository.break_lock()
 
157
        return SuccessfulSmartServerResponse(('ok', ))
 
158
 
 
159
 
 
160
_lsprof_count = 0
 
161
 
145
162
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
146
163
    """Bzr 1.2+ - get parent data for revisions during a graph search."""
147
164
 
180
197
        finally:
181
198
            repository.unlock()
182
199
 
183
 
    def _do_repository_request(self, body_bytes):
184
 
        repository = self._repository
185
 
        revision_ids = set(self._revision_ids)
186
 
        include_missing = 'include-missing:' in revision_ids
187
 
        if include_missing:
188
 
            revision_ids.remove('include-missing:')
189
 
        body_lines = body_bytes.split('\n')
190
 
        search_result, error = self.recreate_search_from_recipe(
191
 
            repository, body_lines)
192
 
        if error is not None:
193
 
            return error
194
 
        # TODO might be nice to start up the search again; but thats not
195
 
        # written or tested yet.
196
 
        client_seen_revs = set(search_result.get_keys())
197
 
        # Always include the requested ids.
198
 
        client_seen_revs.difference_update(revision_ids)
199
 
        lines = []
200
 
        repo_graph = repository.get_graph()
 
200
    def _expand_requested_revs(self, repo_graph, revision_ids, client_seen_revs,
 
201
                               include_missing, max_size=65536):
201
202
        result = {}
202
203
        queried_revs = set()
203
 
        size_so_far = 0
 
204
        estimator = estimate_compressed_size.ZLibEstimator(max_size)
204
205
        next_revs = revision_ids
205
206
        first_loop_done = False
206
207
        while next_revs:
228
229
                    # add parents to the result
229
230
                    result[encoded_id] = parents
230
231
                    # Approximate the serialized cost of this revision_id.
231
 
                    size_so_far += 2 + len(encoded_id) + sum(map(len, parents))
 
232
                    line = '%s %s\n' % (encoded_id, ' '.join(parents))
 
233
                    estimator.add_content(line)
232
234
            # get all the directly asked for parents, and then flesh out to
233
235
            # 64K (compressed) or so. We do one level of depth at a time to
234
236
            # stay in sync with the client. The 250000 magic number is
235
237
            # estimated compression ratio taken from bzr.dev itself.
236
 
            if self.no_extra_results or (
237
 
                first_loop_done and size_so_far > 250000):
 
238
            if self.no_extra_results or (first_loop_done and estimator.full()):
 
239
                trace.mutter('size: %d, z_size: %d'
 
240
                             % (estimator._uncompressed_size_added,
 
241
                                estimator._compressed_size_added))
238
242
                next_revs = set()
239
243
                break
240
244
            # don't query things we've already queried
241
 
            next_revs.difference_update(queried_revs)
 
245
            next_revs = next_revs.difference(queried_revs)
242
246
            first_loop_done = True
 
247
        return result
 
248
 
 
249
    def _do_repository_request(self, body_bytes):
 
250
        repository = self._repository
 
251
        revision_ids = set(self._revision_ids)
 
252
        include_missing = 'include-missing:' in revision_ids
 
253
        if include_missing:
 
254
            revision_ids.remove('include-missing:')
 
255
        body_lines = body_bytes.split('\n')
 
256
        search_result, error = self.recreate_search_from_recipe(
 
257
            repository, body_lines)
 
258
        if error is not None:
 
259
            return error
 
260
        # TODO might be nice to start up the search again; but thats not
 
261
        # written or tested yet.
 
262
        client_seen_revs = set(search_result.get_keys())
 
263
        # Always include the requested ids.
 
264
        client_seen_revs.difference_update(revision_ids)
 
265
 
 
266
        repo_graph = repository.get_graph()
 
267
        result = self._expand_requested_revs(repo_graph, revision_ids,
 
268
                                             client_seen_revs, include_missing)
243
269
 
244
270
        # sorting trivially puts lexographically similar revision ids together.
245
271
        # Compression FTW.
 
272
        lines = []
246
273
        for revision, parents in sorted(result.items()):
247
274
            lines.append(' '.join((revision, ) + tuple(parents)))
248
275
 
313
340
                ('history-incomplete', earliest_revno, earliest_revid))
314
341
 
315
342
 
 
343
class SmartServerRepositoryGetSerializerFormat(SmartServerRepositoryRequest):
 
344
 
 
345
    def do_repository_request(self, repository):
 
346
        """Return the serializer format for this repository.
 
347
 
 
348
        New in 2.5.0.
 
349
 
 
350
        :param repository: The repository to query
 
351
        :return: A smart server response ('ok', FORMAT)
 
352
        """
 
353
        serializer = repository.get_serializer_format()
 
354
        return SuccessfulSmartServerResponse(('ok', serializer))
 
355
 
 
356
 
316
357
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
317
358
 
318
359
    def do_repository_request(self, repository, revision_id):
320
361
 
321
362
        :param repository: The repository to query in.
322
363
        :param revision_id: The utf8 encoded revision_id to lookup.
323
 
        :return: A smart server response of ('ok', ) if the revision is
324
 
            present.
 
364
        :return: A smart server response of ('yes', ) if the revision is
 
365
            present. ('no', ) if it is missing.
325
366
        """
326
367
        if repository.has_revision(revision_id):
327
368
            return SuccessfulSmartServerResponse(('yes', ))
329
370
            return SuccessfulSmartServerResponse(('no', ))
330
371
 
331
372
 
 
373
class SmartServerRequestHasSignatureForRevisionId(
 
374
        SmartServerRepositoryRequest):
 
375
 
 
376
    def do_repository_request(self, repository, revision_id):
 
377
        """Return ok if a signature is present for a revision.
 
378
 
 
379
        Introduced in bzr 2.5.0.
 
380
 
 
381
        :param repository: The repository to query in.
 
382
        :param revision_id: The utf8 encoded revision_id to lookup.
 
383
        :return: A smart server response of ('yes', ) if a
 
384
            signature for the revision is present,
 
385
            ('no', ) if it is missing.
 
386
        """
 
387
        try:
 
388
            if repository.has_signature_for_revision_id(revision_id):
 
389
                return SuccessfulSmartServerResponse(('yes', ))
 
390
            else:
 
391
                return SuccessfulSmartServerResponse(('no', ))
 
392
        except errors.NoSuchRevision:
 
393
            return FailedSmartServerResponse(
 
394
                ('nosuchrevision', revision_id))
 
395
 
 
396
 
332
397
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
333
398
 
334
399
    def do_repository_request(self, repository, revid, committers):
354
419
            decoded_committers = True
355
420
        else:
356
421
            decoded_committers = None
357
 
        stats = repository.gather_stats(decoded_revision_id, decoded_committers)
 
422
        try:
 
423
            stats = repository.gather_stats(decoded_revision_id,
 
424
                decoded_committers)
 
425
        except errors.NoSuchRevision:
 
426
            return FailedSmartServerResponse(('nosuchrevision', revid))
358
427
 
359
428
        body = ''
360
429
        if stats.has_key('committers'):
371
440
        return SuccessfulSmartServerResponse(('ok', ), body)
372
441
 
373
442
 
 
443
class SmartServerRepositoryGetRevisionSignatureText(
 
444
        SmartServerRepositoryRequest):
 
445
    """Return the signature text of a revision.
 
446
 
 
447
    New in 2.5.
 
448
    """
 
449
 
 
450
    def do_repository_request(self, repository, revision_id):
 
451
        """Return the result of repository.get_signature_text().
 
452
 
 
453
        :param repository: The repository to query in.
 
454
        :return: A smart server response of with the signature text as
 
455
            body.
 
456
        """
 
457
        try:
 
458
            text = repository.get_signature_text(revision_id)
 
459
        except errors.NoSuchRevision, err:
 
460
            return FailedSmartServerResponse(
 
461
                ('nosuchrevision', err.revision))
 
462
        return SuccessfulSmartServerResponse(('ok', ), text)
 
463
 
 
464
 
374
465
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
375
466
 
376
467
    def do_repository_request(self, repository):
386
477
            return SuccessfulSmartServerResponse(('no', ))
387
478
 
388
479
 
 
480
class SmartServerRepositoryMakeWorkingTrees(SmartServerRepositoryRequest):
 
481
 
 
482
    def do_repository_request(self, repository):
 
483
        """Return the result of repository.make_working_trees().
 
484
 
 
485
        Introduced in bzr 2.5.0.
 
486
 
 
487
        :param repository: The repository to query in.
 
488
        :return: A smart server response of ('yes', ) if the repository uses
 
489
            working trees, and ('no', ) if it is not.
 
490
        """
 
491
        if repository.make_working_trees():
 
492
            return SuccessfulSmartServerResponse(('yes', ))
 
493
        else:
 
494
            return SuccessfulSmartServerResponse(('no', ))
 
495
 
 
496
 
389
497
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
390
498
 
391
499
    def do_repository_request(self, repository, token=''):
672
780
        return SuccessfulSmartServerResponse(('ok',))
673
781
 
674
782
 
 
783
class SmartServerRepositoryGetPhysicalLockStatus(SmartServerRepositoryRequest):
 
784
    """Get the physical lock status for a repository.
 
785
 
 
786
    New in 2.5.
 
787
    """
 
788
 
 
789
    def do_repository_request(self, repository):
 
790
        if repository.get_physical_lock_status():
 
791
            return SuccessfulSmartServerResponse(('yes', ))
 
792
        else:
 
793
            return SuccessfulSmartServerResponse(('no', ))
 
794
 
 
795
 
675
796
class SmartServerRepositorySetMakeWorkingTrees(SmartServerRepositoryRequest):
676
797
 
677
798
    def do_repository_request(self, repository, str_bool_new_value):
840
961
        self.do_insert_stream_request(repository, resume_tokens)
841
962
 
842
963
 
 
964
class SmartServerRepositoryAddSignatureText(SmartServerRepositoryRequest):
 
965
    """Add a revision signature text.
 
966
 
 
967
    New in 2.5.
 
968
    """
 
969
 
 
970
    def do_repository_request(self, repository, lock_token, revision_id,
 
971
            *write_group_tokens):
 
972
        """Add a revision signature text.
 
973
 
 
974
        :param repository: Repository to operate on
 
975
        :param lock_token: Lock token
 
976
        :param revision_id: Revision for which to add signature
 
977
        :param write_group_tokens: Write group tokens
 
978
        """
 
979
        self._lock_token = lock_token
 
980
        self._revision_id = revision_id
 
981
        self._write_group_tokens = write_group_tokens
 
982
        return None
 
983
 
 
984
    def do_body(self, body_bytes):
 
985
        """Add a signature text.
 
986
 
 
987
        :param body_bytes: GPG signature text
 
988
        :return: SuccessfulSmartServerResponse with arguments 'ok' and
 
989
            the list of new write group tokens.
 
990
        """
 
991
        self._repository.lock_write(token=self._lock_token)
 
992
        try:
 
993
            self._repository.resume_write_group(self._write_group_tokens)
 
994
            try:
 
995
                self._repository.add_signature_text(self._revision_id,
 
996
                    body_bytes)
 
997
            finally:
 
998
                new_write_group_tokens = self._repository.suspend_write_group()
 
999
        finally:
 
1000
            self._repository.unlock()
 
1001
        return SuccessfulSmartServerResponse(
 
1002
            ('ok', ) + tuple(new_write_group_tokens))
 
1003
 
 
1004
 
 
1005
class SmartServerRepositoryStartWriteGroup(SmartServerRepositoryRequest):
 
1006
    """Start a write group.
 
1007
 
 
1008
    New in 2.5.
 
1009
    """
 
1010
 
 
1011
    def do_repository_request(self, repository, lock_token):
 
1012
        """Start a write group."""
 
1013
        repository.lock_write(token=lock_token)
 
1014
        try:
 
1015
            repository.start_write_group()
 
1016
            try:
 
1017
                tokens = repository.suspend_write_group()
 
1018
            except errors.UnsuspendableWriteGroup:
 
1019
                return FailedSmartServerResponse(('UnsuspendableWriteGroup',))
 
1020
        finally:
 
1021
            repository.unlock()
 
1022
        return SuccessfulSmartServerResponse(('ok', tokens))
 
1023
 
 
1024
 
 
1025
class SmartServerRepositoryCommitWriteGroup(SmartServerRepositoryRequest):
 
1026
    """Commit a write group.
 
1027
 
 
1028
    New in 2.5.
 
1029
    """
 
1030
 
 
1031
    def do_repository_request(self, repository, lock_token,
 
1032
            write_group_tokens):
 
1033
        """Commit a write group."""
 
1034
        repository.lock_write(token=lock_token)
 
1035
        try:
 
1036
            try:
 
1037
                repository.resume_write_group(write_group_tokens)
 
1038
            except errors.UnresumableWriteGroup, e:
 
1039
                return FailedSmartServerResponse(
 
1040
                    ('UnresumableWriteGroup', e.write_groups, e.reason))
 
1041
            try:
 
1042
                repository.commit_write_group()
 
1043
            except:
 
1044
                write_group_tokens = repository.suspend_write_group()
 
1045
                # FIXME JRV 2011-11-19: What if the write_group_tokens
 
1046
                # have changed?
 
1047
                raise
 
1048
        finally:
 
1049
            repository.unlock()
 
1050
        return SuccessfulSmartServerResponse(('ok', ))
 
1051
 
 
1052
 
 
1053
class SmartServerRepositoryAbortWriteGroup(SmartServerRepositoryRequest):
 
1054
    """Abort a write group.
 
1055
 
 
1056
    New in 2.5.
 
1057
    """
 
1058
 
 
1059
    def do_repository_request(self, repository, lock_token, write_group_tokens):
 
1060
        """Abort a write group."""
 
1061
        repository.lock_write(token=lock_token)
 
1062
        try:
 
1063
            try:
 
1064
                repository.resume_write_group(write_group_tokens)
 
1065
            except errors.UnresumableWriteGroup, e:
 
1066
                return FailedSmartServerResponse(
 
1067
                    ('UnresumableWriteGroup', e.write_groups, e.reason))
 
1068
                repository.abort_write_group()
 
1069
        finally:
 
1070
            repository.unlock()
 
1071
        return SuccessfulSmartServerResponse(('ok', ))
 
1072
 
 
1073
 
 
1074
class SmartServerRepositoryCheckWriteGroup(SmartServerRepositoryRequest):
 
1075
    """Check that a write group is still valid.
 
1076
 
 
1077
    New in 2.5.
 
1078
    """
 
1079
 
 
1080
    def do_repository_request(self, repository, lock_token, write_group_tokens):
 
1081
        """Abort a write group."""
 
1082
        repository.lock_write(token=lock_token)
 
1083
        try:
 
1084
            try:
 
1085
                repository.resume_write_group(write_group_tokens)
 
1086
            except errors.UnresumableWriteGroup, e:
 
1087
                return FailedSmartServerResponse(
 
1088
                    ('UnresumableWriteGroup', e.write_groups, e.reason))
 
1089
            else:
 
1090
                repository.suspend_write_group()
 
1091
        finally:
 
1092
            repository.unlock()
 
1093
        return SuccessfulSmartServerResponse(('ok', ))
 
1094
 
 
1095
 
 
1096
class SmartServerRepositoryAllRevisionIds(SmartServerRepositoryRequest):
 
1097
    """Retrieve all of the revision ids in a repository.
 
1098
 
 
1099
    New in 2.5.
 
1100
    """
 
1101
 
 
1102
    def do_repository_request(self, repository):
 
1103
        revids = repository.all_revision_ids()
 
1104
        return SuccessfulSmartServerResponse(("ok", ), "\n".join(revids))
 
1105
 
 
1106
 
 
1107
class SmartServerRepositoryReconcile(SmartServerRepositoryRequest):
 
1108
    """Reconcile a repository.
 
1109
 
 
1110
    New in 2.5.
 
1111
    """
 
1112
 
 
1113
    def do_repository_request(self, repository, lock_token):
 
1114
        try:
 
1115
            repository.lock_write(token=lock_token)
 
1116
        except errors.TokenLockingNotSupported, e:
 
1117
            return FailedSmartServerResponse(
 
1118
                ('TokenLockingNotSupported', ))
 
1119
        try:
 
1120
            reconciler = repository.reconcile()
 
1121
        finally:
 
1122
            repository.unlock()
 
1123
        body = [
 
1124
            "garbage_inventories: %d\n" % reconciler.garbage_inventories,
 
1125
            "inconsistent_parents: %d\n" % reconciler.inconsistent_parents,
 
1126
            ]
 
1127
        return SuccessfulSmartServerResponse(('ok', ), "".join(body))
 
1128
 
 
1129
 
 
1130
class SmartServerRepositoryPack(SmartServerRepositoryRequest):
 
1131
    """Pack a repository.
 
1132
 
 
1133
    New in 2.5.
 
1134
    """
 
1135
 
 
1136
    def do_repository_request(self, repository, lock_token, clean_obsolete_packs):
 
1137
        self._repository = repository
 
1138
        self._lock_token = lock_token
 
1139
        if clean_obsolete_packs == 'True':
 
1140
            self._clean_obsolete_packs = True
 
1141
        else:
 
1142
            self._clean_obsolete_packs = False
 
1143
        return None
 
1144
 
 
1145
    def do_body(self, body_bytes):
 
1146
        if body_bytes == "":
 
1147
            hint = None
 
1148
        else:
 
1149
            hint = body_bytes.splitlines()
 
1150
        self._repository.lock_write(token=self._lock_token)
 
1151
        try:
 
1152
            self._repository.pack(hint, self._clean_obsolete_packs)
 
1153
        finally:
 
1154
            self._repository.unlock()
 
1155
        return SuccessfulSmartServerResponse(("ok", ), )
 
1156
 
 
1157
 
 
1158
class SmartServerRepositoryIterFilesBytes(SmartServerRepositoryRequest):
 
1159
    """Iterate over the contents of files.
 
1160
 
 
1161
    The client sends a list of desired files to stream, one
 
1162
    per line, and as tuples of file id and revision, separated by
 
1163
    \0.
 
1164
 
 
1165
    The server replies with a stream. Each entry is preceded by a header,
 
1166
    which can either be:
 
1167
 
 
1168
    * "ok\x00IDX\n" where IDX is the index of the entry in the desired files
 
1169
        list sent by the client. This header is followed by the contents of
 
1170
        the file, bzip2-compressed.
 
1171
    * "absent\x00FILEID\x00REVISION\x00IDX" to indicate a text is missing.
 
1172
        The client can then raise an appropriate RevisionNotPresent error
 
1173
        or check its fallback repositories.
 
1174
 
 
1175
    New in 2.5.
 
1176
    """
 
1177
 
 
1178
    def body_stream(self, repository, desired_files):
 
1179
        self._repository.lock_read()
 
1180
        try:
 
1181
            text_keys = {}
 
1182
            for i, key in enumerate(desired_files):
 
1183
                text_keys[key] = i
 
1184
            for record in repository.texts.get_record_stream(text_keys,
 
1185
                    'unordered', True):
 
1186
                identifier = text_keys[record.key]
 
1187
                if record.storage_kind == 'absent':
 
1188
                    yield "absent\0%s\0%s\0%d\n" % (record.key[0],
 
1189
                        record.key[1], identifier)
 
1190
                    # FIXME: Way to abort early?
 
1191
                    continue
 
1192
                yield "ok\0%d\n" % identifier
 
1193
                compressor = zlib.compressobj()
 
1194
                for bytes in record.get_bytes_as('chunked'):
 
1195
                    data = compressor.compress(bytes)
 
1196
                    if data:
 
1197
                        yield data
 
1198
                data = compressor.flush()
 
1199
                if data:
 
1200
                    yield data
 
1201
        finally:
 
1202
            self._repository.unlock()
 
1203
 
 
1204
    def do_body(self, body_bytes):
 
1205
        desired_files = [
 
1206
            tuple(l.split("\0")) for l in body_bytes.splitlines()]
 
1207
        return SuccessfulSmartServerResponse(('ok', ),
 
1208
            body_stream=self.body_stream(self._repository, desired_files))
 
1209
 
 
1210
    def do_repository_request(self, repository):
 
1211
        # Signal that we want a body
 
1212
        return None
 
1213
 
 
1214
 
 
1215
class SmartServerRepositoryIterRevisions(SmartServerRepositoryRequest):
 
1216
    """Stream a list of revisions.
 
1217
 
 
1218
    The client sends a list of newline-separated revision ids in the
 
1219
    body of the request and the server replies with the serializer format,
 
1220
    and a stream of bzip2-compressed revision texts (using the specified
 
1221
    serializer format).
 
1222
 
 
1223
    Any revisions the server does not have are omitted from the stream.
 
1224
 
 
1225
    New in 2.5.
 
1226
    """
 
1227
 
 
1228
    def do_repository_request(self, repository):
 
1229
        self._repository = repository
 
1230
        # Signal there is a body
 
1231
        return None
 
1232
 
 
1233
    def do_body(self, body_bytes):
 
1234
        revision_ids = body_bytes.split("\n")
 
1235
        return SuccessfulSmartServerResponse(
 
1236
            ('ok', self._repository.get_serializer_format()),
 
1237
            body_stream=self.body_stream(self._repository, revision_ids))
 
1238
 
 
1239
    def body_stream(self, repository, revision_ids):
 
1240
        self._repository.lock_read()
 
1241
        try:
 
1242
            for record in repository.revisions.get_record_stream(
 
1243
                [(revid,) for revid in revision_ids], 'unordered', True):
 
1244
                if record.storage_kind == 'absent':
 
1245
                    continue
 
1246
                yield zlib.compress(record.get_bytes_as('fulltext'))
 
1247
        finally:
 
1248
            self._repository.unlock()
 
1249
 
 
1250
 
 
1251
class SmartServerRepositoryGetInventories(SmartServerRepositoryRequest):
 
1252
    """Get the inventory deltas for a set of revision ids.
 
1253
 
 
1254
    This accepts a list of revision ids, and then sends a chain
 
1255
    of deltas for the inventories of those revisions. The first
 
1256
    revision will be empty.
 
1257
 
 
1258
    The server writes back zlibbed serialized inventory deltas,
 
1259
    in the ordering specified. The base for each delta is the
 
1260
    inventory generated by the previous delta.
 
1261
 
 
1262
    New in 2.5.
 
1263
    """
 
1264
 
 
1265
    def _inventory_delta_stream(self, repository, ordering, revids):
 
1266
        prev_inv = _mod_inventory.Inventory(root_id=None,
 
1267
            revision_id=_mod_revision.NULL_REVISION)
 
1268
        serializer = inventory_delta.InventoryDeltaSerializer(
 
1269
            repository.supports_rich_root(),
 
1270
            repository._format.supports_tree_reference)
 
1271
        repository.lock_read()
 
1272
        try:
 
1273
            for inv, revid in repository._iter_inventories(revids, ordering):
 
1274
                if inv is None:
 
1275
                    continue
 
1276
                inv_delta = inv._make_delta(prev_inv)
 
1277
                lines = serializer.delta_to_lines(
 
1278
                    prev_inv.revision_id, inv.revision_id, inv_delta)
 
1279
                yield ChunkedContentFactory(inv.revision_id, None, None, lines)
 
1280
                prev_inv = inv
 
1281
        finally:
 
1282
            repository.unlock()
 
1283
 
 
1284
    def body_stream(self, repository, ordering, revids):
 
1285
        substream = self._inventory_delta_stream(repository,
 
1286
            ordering, revids)
 
1287
        return _stream_to_byte_stream([('inventory-deltas', substream)],
 
1288
            repository._format)
 
1289
 
 
1290
    def do_body(self, body_bytes):
 
1291
        return SuccessfulSmartServerResponse(('ok', ),
 
1292
            body_stream=self.body_stream(self._repository, self._ordering,
 
1293
                body_bytes.splitlines()))
 
1294
 
 
1295
    def do_repository_request(self, repository, ordering):
 
1296
        if ordering == 'unordered':
 
1297
            # inventory deltas for a topologically sorted stream
 
1298
            # are likely to be smaller
 
1299
            ordering = 'topological'
 
1300
        self._ordering = ordering
 
1301
        # Signal that we want a body
 
1302
        return None