~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Aaron Bentley
  • Date: 2008-03-03 16:52:41 UTC
  • mfrom: (3144.3.11 fix-conflict-handling)
  • mto: This revision was merged to the branch mainline in revision 3250.
  • Revision ID: aaron@aaronbentley.com-20080303165241-0k2c7ggs6kr9q6hf
Merge with fix-conflict-handling

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Server-side repository related request implmentations."""
18
18
 
 
19
import bz2
19
20
from cStringIO import StringIO
20
21
import os
21
22
import sys
58
59
        # is expected)
59
60
        return None
60
61
 
 
62
    def recreate_search(self, repository, recipe_bytes):
 
63
        lines = recipe_bytes.split('\n')
 
64
        start_keys = set(lines[0].split(' '))
 
65
        exclude_keys = set(lines[1].split(' '))
 
66
        revision_count = int(lines[2])
 
67
        repository.lock_read()
 
68
        try:
 
69
            search = repository.get_graph()._make_breadth_first_searcher(
 
70
                start_keys)
 
71
            while True:
 
72
                try:
 
73
                    next_revs = search.next()
 
74
                except StopIteration:
 
75
                    break
 
76
                search.stop_searching_any(exclude_keys.intersection(next_revs))
 
77
            search_result = search.get_result()
 
78
            if search_result.get_recipe()[2] != revision_count:
 
79
                # we got back a different amount of data than expected, this
 
80
                # gets reported as NoSuchRevision, because less revisions
 
81
                # indicates missing revisions, and more should never happen as
 
82
                # the excludes list considers ghosts and ensures that ghost
 
83
                # filling races are not a problem.
 
84
                return (None, FailedSmartServerResponse(('NoSuchRevision',)))
 
85
            return (search, None)
 
86
        finally:
 
87
            repository.unlock()
 
88
 
61
89
 
62
90
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
 
91
    """Bzr 1.2+ - get parent data for revisions during a graph search."""
63
92
    
64
93
    def do_repository_request(self, repository, *revision_ids):
65
 
        repository.lock_read()
66
 
        try:
67
 
            return self._do_repository_request(repository, revision_ids)
68
 
        finally:
69
 
            repository.unlock()
70
 
 
71
 
    def _do_repository_request(self, repository, revision_ids):
72
94
        """Get parent details for some revisions.
73
95
        
74
96
        All the parents for revision_ids are returned. Additionally up to 64KB
75
97
        of additional parent data found by performing a breadth first search
76
 
        from revision_ids is returned.
 
98
        from revision_ids is returned. The verb takes a body containing the
 
99
        current search state, see do_body for details.
77
100
 
78
101
        :param repository: The repository to query in.
79
102
        :param revision_ids: The utf8 encoded revision_id to answer for.
 
103
        """
 
104
        self._revision_ids = revision_ids
 
105
        return None # Signal that we want a body.
 
106
 
 
107
    def do_body(self, body_bytes):
 
108
        """Process the current search state and perform the parent lookup.
 
109
 
80
110
        :return: A smart server response where the body contains an utf8
81
 
            encoded flattened list of the parents of the revisions, (the same
82
 
            format as Repository.get_revision_graph).
 
111
            encoded flattened list of the parents of the revisions (the same
 
112
            format as Repository.get_revision_graph) which has been bz2
 
113
            compressed.
83
114
        """
 
115
        repository = self._repository
 
116
        repository.lock_read()
 
117
        try:
 
118
            return self._do_repository_request(body_bytes)
 
119
        finally:
 
120
            repository.unlock()
 
121
 
 
122
    def _do_repository_request(self, body_bytes):
 
123
        repository = self._repository
 
124
        revision_ids = set(self._revision_ids)
 
125
        search, error = self.recreate_search(repository, body_bytes)
 
126
        if error is not None:
 
127
            return error
 
128
        # TODO might be nice to start up the search again; but thats not
 
129
        # written or tested yet.
 
130
        client_seen_revs = set(search.get_result().get_keys())
 
131
        # Always include the requested ids.
 
132
        client_seen_revs.difference_update(revision_ids)
84
133
        lines = []
85
134
        repo_graph = repository.get_graph()
86
135
        result = {}
96
145
                # adjust for the wire
97
146
                if parents == (_mod_revision.NULL_REVISION,):
98
147
                    parents = ()
99
 
                # add parents to the result
100
 
                result[revision_id] = parents
101
148
                # prepare the next query
102
149
                next_revs.update(parents)
103
 
                # Approximate the serialized cost of this revision_id.
104
 
                size_so_far += 2 + len(revision_id) + sum(map(len, parents))
105
 
                # get all the directly asked for parents, and then flesh out to
106
 
                # 64K or so.
107
 
                if first_loop_done and size_so_far > 65000:
108
 
                    next_revs = set()
109
 
                    break
 
150
                if revision_id not in client_seen_revs:
 
151
                    # Client does not have this revision, give it to it.
 
152
                    # add parents to the result
 
153
                    result[revision_id] = parents
 
154
                    # Approximate the serialized cost of this revision_id.
 
155
                    size_so_far += 2 + len(revision_id) + sum(map(len, parents))
 
156
            # get all the directly asked for parents, and then flesh out to
 
157
            # 64K (compressed) or so. We do one level of depth at a time to
 
158
            # stay in sync with the client. The 250000 magic number is
 
159
            # estimated compression ratio taken from bzr.dev itself.
 
160
            if first_loop_done and size_so_far > 250000:
 
161
                next_revs = set()
 
162
                break
110
163
            # don't query things we've already queried
111
164
            next_revs.difference_update(queried_revs)
112
165
            first_loop_done = True
113
166
 
114
 
        for revision, parents in result.items():
 
167
        # sorting trivially puts lexographically similar revision ids together.
 
168
        # Compression FTW.
 
169
        for revision, parents in sorted(result.items()):
115
170
            lines.append(' '.join((revision, ) + tuple(parents)))
116
171
 
117
 
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
 
172
        return SuccessfulSmartServerResponse(
 
173
            ('ok', ), bz2.compress('\n'.join(lines)))
118
174
 
119
175
 
120
176
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
342
398
    """Bzr 1.1+ streaming pull."""
343
399
 
344
400
    def do_body(self, body_bytes):
345
 
        lines = body_bytes.split('\n')
346
 
        start_keys = set(lines[0].split(' '))
347
 
        exclude_keys = set(lines[1].split(' '))
348
 
        revision_count = int(lines[2])
349
401
        repository = self._repository
350
402
        repository.lock_read()
351
403
        try:
352
 
            search = repository.get_graph()._make_breadth_first_searcher(
353
 
                start_keys)
354
 
            while True:
355
 
                try:
356
 
                    next_revs = search.next()
357
 
                except StopIteration:
358
 
                    break
359
 
                search.stop_searching_any(exclude_keys.intersection(next_revs))
360
 
            search_result = search.get_result()
361
 
            if search_result.get_recipe()[2] != revision_count:
362
 
                # we got back a different amount of data than expected, this
363
 
                # gets reported as NoSuchRevision, because less revisions
364
 
                # indicates missing revisions, and more should never happen as
365
 
                # the excludes list considers ghosts and ensures that ghost
366
 
                # filling races are not a problem.
367
 
                return FailedSmartServerResponse(('NoSuchRevision',))
368
 
            stream = repository.get_data_stream_for_search(search_result)
 
404
            search, error = self.recreate_search(repository, body_bytes)
 
405
            if error is not None:
 
406
                return error
 
407
            stream = repository.get_data_stream_for_search(search.get_result())
369
408
        except Exception:
 
409
            # On non-error, unlocking is done by the body stream handler.
370
410
            repository.unlock()
371
411
            raise
372
412
        return SuccessfulSmartServerResponse(('ok',),