~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Martin Pool
  • Date: 2005-05-03 08:00:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050503080027-908edb5b39982198
doc

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
 
        repository.leave_lock_in_place()
172
 
        repository.unlock()
173
 
        if token is None:
174
 
            token = ''
175
 
        return SuccessfulSmartServerResponse(('ok', token))
176
 
 
177
 
 
178
 
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
179
 
 
180
 
    def do_repository_request(self, repository, token):
181
 
        try:
182
 
            repository.lock_write(token=token)
183
 
        except errors.TokenMismatch, e:
184
 
            return FailedSmartServerResponse(('TokenMismatch',))
185
 
        repository.dont_leave_lock_in_place()
186
 
        repository.unlock()
187
 
        return SuccessfulSmartServerResponse(('ok',))
188
 
 
189
 
 
190
 
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
191
 
    """Get the raw repository files as a tarball.
192
 
 
193
 
    The returned tarball contains a .bzr control directory which in turn
194
 
    contains a repository.
195
 
    
196
 
    This takes one parameter, compression, which currently must be 
197
 
    "", "gz", or "bz2".
198
 
 
199
 
    This is used to implement the Repository.copy_content_into operation.
200
 
    """
201
 
 
202
 
    def do_repository_request(self, repository, compression):
203
 
        from bzrlib import osutils
204
 
        repo_transport = repository.control_files._transport
205
 
        tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
206
 
        try:
207
 
            controldir_name = tmp_dirname + '/.bzr'
208
 
            return self._tarfile_response(controldir_name, compression)
209
 
        finally:
210
 
            osutils.rmtree(tmp_dirname)
211
 
 
212
 
    def _copy_to_tempdir(self, from_repo):
213
 
        tmp_dirname = tempfile.mkdtemp(prefix='tmpbzrclone')
214
 
        tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
215
 
        tmp_repo = from_repo._format.initialize(tmp_bzrdir)
216
 
        from_repo.copy_content_into(tmp_repo)
217
 
        return tmp_dirname, tmp_repo
218
 
 
219
 
    def _tarfile_response(self, tmp_dirname, compression):
220
 
        temp = tempfile.NamedTemporaryFile()
221
 
        try:
222
 
            self._tarball_of_dir(tmp_dirname, compression, temp.file)
223
 
            # all finished; write the tempfile out to the network
224
 
            temp.seek(0)
225
 
            return SuccessfulSmartServerResponse(('ok',), temp.read())
226
 
            # FIXME: Don't read the whole thing into memory here; rather stream it
227
 
            # out from the file onto the network. mbp 20070411
228
 
        finally:
229
 
            temp.close()
230
 
 
231
 
    def _tarball_of_dir(self, dirname, compression, ofile):
232
 
        filename = os.path.basename(ofile.name)
233
 
        tarball = tarfile.open(fileobj=ofile, name=filename,
234
 
            mode='w|' + compression)
235
 
        try:
236
 
            # The tarball module only accepts ascii names, and (i guess)
237
 
            # packs them with their 8bit names.  We know all the files
238
 
            # within the repository have ASCII names so the should be safe
239
 
            # to pack in.
240
 
            dirname = dirname.encode(sys.getfilesystemencoding())
241
 
            # python's tarball module includes the whole path by default so
242
 
            # override it
243
 
            assert dirname.endswith('.bzr')
244
 
            tarball.add(dirname, '.bzr') # recursive by default
245
 
        finally:
246
 
            tarball.close()
247
 
 
248
 
 
249
 
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
250
 
 
251
 
    def do_repository_request(self, repository, *revision_ids):
252
 
        stream = repository.get_data_stream(revision_ids)
253
 
        filelike = StringIO()
254
 
        pack = ContainerWriter(filelike.write)
255
 
        pack.begin()
256
 
        try:
257
 
            for name_tuple, bytes in stream:
258
 
                pack.add_bytes_record(bytes, [name_tuple])
259
 
        except errors.RevisionNotPresent, e:
260
 
            return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
261
 
        pack.end()
262
 
        return SuccessfulSmartServerResponse(('ok',), filelike.getvalue())
263