/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Robert Collins
  • Date: 2008-01-14 04:46:08 UTC
  • mto: This revision was merged to the branch mainline in revision 3184.
  • Revision ID: robertc@robertcollins.net-20080114044608-bmse3mmsnp1663rf
Create new smart server verb Repository.get_parent_map.

Show diffs side-by-side

added added

removed removed

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