~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-12-07 03:30:29 UTC
  • mfrom: (3079.1.1 criss-cross)
  • Revision ID: pqm@pqm.ubuntu.com-20071207033029-7tx9ezbg3nlk3io1
(Alexander Belchenko) topic for criss-cross should have title,
        otherwise autogenerated bzr_man has problems

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 ContainerWriter
 
28
from bzrlib.smart.request import (
 
29
    FailedSmartServerResponse,
 
30
    SmartServerRequest,
 
31
    SuccessfulSmartServerResponse,
 
32
    )
 
33
 
 
34
 
 
35
class SmartServerRepositoryRequest(SmartServerRequest):
 
36
    """Common base class for Repository requests."""
 
37
 
 
38
    def do(self, path, *args):
 
39
        """Execute a repository request.
 
40
        
 
41
        The repository must be at the exact path - no searching is done.
 
42
 
 
43
        The actual logic is delegated to self.do_repository_request.
 
44
 
 
45
        :param path: The path for the repository.
 
46
        :return: A smart server from self.do_repository_request().
 
47
        """
 
48
        transport = self._backing_transport.clone(path)
 
49
        bzrdir = BzrDir.open_from_transport(transport)
 
50
        repository = bzrdir.open_repository()
 
51
        return self.do_repository_request(repository, *args)
 
52
 
 
53
 
 
54
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
 
55
    
 
56
    def do_repository_request(self, repository, revision_id):
 
57
        """Return the result of repository.get_revision_graph(revision_id).
 
58
        
 
59
        :param repository: The repository to query in.
 
60
        :param revision_id: The utf8 encoded revision_id to get a graph from.
 
61
        :return: A smart server response where the body contains an utf8
 
62
            encoded flattened list of the revision graph.
 
63
        """
 
64
        if not revision_id:
 
65
            revision_id = None
 
66
 
 
67
        lines = []
 
68
        try:
 
69
            revision_graph = repository.get_revision_graph(revision_id)
 
70
        except errors.NoSuchRevision:
 
71
            # Note that we return an empty body, rather than omitting the body.
 
72
            # This way the client knows that it can always expect to find a body
 
73
            # in the response for this method, even in the error case.
 
74
            return FailedSmartServerResponse(('nosuchrevision', revision_id), '')
 
75
 
 
76
        for revision, parents in revision_graph.items():
 
77
            lines.append(' '.join((revision, ) + tuple(parents)))
 
78
 
 
79
        return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
 
80
 
 
81
 
 
82
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
 
83
 
 
84
    def do_repository_request(self, repository, revision_id):
 
85
        """Return ok if a specific revision is in the repository at path.
 
86
 
 
87
        :param repository: The repository to query in.
 
88
        :param revision_id: The utf8 encoded revision_id to lookup.
 
89
        :return: A smart server response of ('ok', ) if the revision is
 
90
            present.
 
91
        """
 
92
        if repository.has_revision(revision_id):
 
93
            return SuccessfulSmartServerResponse(('yes', ))
 
94
        else:
 
95
            return SuccessfulSmartServerResponse(('no', ))
 
96
 
 
97
 
 
98
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
 
99
 
 
100
    def do_repository_request(self, repository, revid, committers):
 
101
        """Return the result of repository.gather_stats().
 
102
 
 
103
        :param repository: The repository to query in.
 
104
        :param revid: utf8 encoded rev id or an empty string to indicate None
 
105
        :param committers: 'yes' or 'no'.
 
106
 
 
107
        :return: A SmartServerResponse ('ok',), a encoded body looking like
 
108
              committers: 1
 
109
              firstrev: 1234.230 0
 
110
              latestrev: 345.700 3600
 
111
              revisions: 2
 
112
              size:45
 
113
 
 
114
              But containing only fields returned by the gather_stats() call
 
115
        """
 
116
        if revid == '':
 
117
            decoded_revision_id = None
 
118
        else:
 
119
            decoded_revision_id = revid
 
120
        if committers == 'yes':
 
121
            decoded_committers = True
 
122
        else:
 
123
            decoded_committers = None
 
124
        stats = repository.gather_stats(decoded_revision_id, decoded_committers)
 
125
 
 
126
        body = ''
 
127
        if stats.has_key('committers'):
 
128
            body += 'committers: %d\n' % stats['committers']
 
129
        if stats.has_key('firstrev'):
 
130
            body += 'firstrev: %.3f %d\n' % stats['firstrev']
 
131
        if stats.has_key('latestrev'):
 
132
             body += 'latestrev: %.3f %d\n' % stats['latestrev']
 
133
        if stats.has_key('revisions'):
 
134
            body += 'revisions: %d\n' % stats['revisions']
 
135
        if stats.has_key('size'):
 
136
            body += 'size: %d\n' % stats['size']
 
137
 
 
138
        return SuccessfulSmartServerResponse(('ok', ), body)
 
139
 
 
140
 
 
141
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
 
142
 
 
143
    def do_repository_request(self, repository):
 
144
        """Return the result of repository.is_shared().
 
145
 
 
146
        :param repository: The repository to query in.
 
147
        :return: A smart server response of ('yes', ) if the repository is
 
148
            shared, and ('no', ) if it is not.
 
149
        """
 
150
        if repository.is_shared():
 
151
            return SuccessfulSmartServerResponse(('yes', ))
 
152
        else:
 
153
            return SuccessfulSmartServerResponse(('no', ))
 
154
 
 
155
 
 
156
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
 
157
 
 
158
    def do_repository_request(self, repository, token=''):
 
159
        # XXX: this probably should not have a token.
 
160
        if token == '':
 
161
            token = None
 
162
        try:
 
163
            token = repository.lock_write(token=token)
 
164
        except errors.LockContention, e:
 
165
            return FailedSmartServerResponse(('LockContention',))
 
166
        except errors.UnlockableTransport:
 
167
            return FailedSmartServerResponse(('UnlockableTransport',))
 
168
        except errors.LockFailed, e:
 
169
            return FailedSmartServerResponse(('LockFailed',
 
170
                str(e.lock), str(e.why)))
 
171
        if token is not None:
 
172
            repository.leave_lock_in_place()
 
173
        repository.unlock()
 
174
        if token is None:
 
175
            token = ''
 
176
        return SuccessfulSmartServerResponse(('ok', token))
 
177
 
 
178
 
 
179
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
 
180
 
 
181
    def do_repository_request(self, repository, token):
 
182
        try:
 
183
            repository.lock_write(token=token)
 
184
        except errors.TokenMismatch, e:
 
185
            return FailedSmartServerResponse(('TokenMismatch',))
 
186
        repository.dont_leave_lock_in_place()
 
187
        repository.unlock()
 
188
        return SuccessfulSmartServerResponse(('ok',))
 
189
 
 
190
 
 
191
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
 
192
    """Get the raw repository files as a tarball.
 
193
 
 
194
    The returned tarball contains a .bzr control directory which in turn
 
195
    contains a repository.
 
196
    
 
197
    This takes one parameter, compression, which currently must be 
 
198
    "", "gz", or "bz2".
 
199
 
 
200
    This is used to implement the Repository.copy_content_into operation.
 
201
    """
 
202
 
 
203
    def do_repository_request(self, repository, compression):
 
204
        from bzrlib import osutils
 
205
        repo_transport = repository.control_files._transport
 
206
        tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
 
207
        try:
 
208
            controldir_name = tmp_dirname + '/.bzr'
 
209
            return self._tarfile_response(controldir_name, compression)
 
210
        finally:
 
211
            osutils.rmtree(tmp_dirname)
 
212
 
 
213
    def _copy_to_tempdir(self, from_repo):
 
214
        tmp_dirname = tempfile.mkdtemp(prefix='tmpbzrclone')
 
215
        tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
 
216
        tmp_repo = from_repo._format.initialize(tmp_bzrdir)
 
217
        from_repo.copy_content_into(tmp_repo)
 
218
        return tmp_dirname, tmp_repo
 
219
 
 
220
    def _tarfile_response(self, tmp_dirname, compression):
 
221
        temp = tempfile.NamedTemporaryFile()
 
222
        try:
 
223
            self._tarball_of_dir(tmp_dirname, compression, temp.file)
 
224
            # all finished; write the tempfile out to the network
 
225
            temp.seek(0)
 
226
            return SuccessfulSmartServerResponse(('ok',), temp.read())
 
227
            # FIXME: Don't read the whole thing into memory here; rather stream it
 
228
            # out from the file onto the network. mbp 20070411
 
229
        finally:
 
230
            temp.close()
 
231
 
 
232
    def _tarball_of_dir(self, dirname, compression, ofile):
 
233
        filename = os.path.basename(ofile.name)
 
234
        tarball = tarfile.open(fileobj=ofile, name=filename,
 
235
            mode='w|' + compression)
 
236
        try:
 
237
            # The tarball module only accepts ascii names, and (i guess)
 
238
            # packs them with their 8bit names.  We know all the files
 
239
            # within the repository have ASCII names so the should be safe
 
240
            # to pack in.
 
241
            dirname = dirname.encode(sys.getfilesystemencoding())
 
242
            # python's tarball module includes the whole path by default so
 
243
            # override it
 
244
            assert dirname.endswith('.bzr')
 
245
            tarball.add(dirname, '.bzr') # recursive by default
 
246
        finally:
 
247
            tarball.close()
 
248
 
 
249
 
 
250
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
 
251
 
 
252
    def do_repository_request(self, repository, *revision_ids):
 
253
        repository.lock_read()
 
254
        try:
 
255
            return self._do_repository_request(repository, revision_ids)
 
256
        finally:
 
257
            repository.unlock()
 
258
 
 
259
    def _do_repository_request(self, repository, revision_ids):
 
260
        stream = repository.get_data_stream(revision_ids)
 
261
        filelike = StringIO()
 
262
        pack = ContainerWriter(filelike.write)
 
263
        pack.begin()
 
264
        try:
 
265
            for name_tuple, bytes in stream:
 
266
                pack.add_bytes_record(bytes, [name_tuple])
 
267
        except errors.RevisionNotPresent, e:
 
268
            return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
 
269
        pack.end()
 
270
        return SuccessfulSmartServerResponse(('ok',), filelike.getvalue())
 
271