~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Martin Pool
  • Date: 2005-06-22 06:37:43 UTC
  • Revision ID: mbp@sourcefrog.net-20050622063743-e395f04c4db8977f
- move old blackbox code from testbzr into bzrlib.selftest.blackbox

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