~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

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()