~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: John Arbash Meinel
  • Date: 2007-11-13 20:37:09 UTC
  • mto: This revision was merged to the branch mainline in revision 3001.
  • Revision ID: john@arbash-meinel.com-20071113203709-kysdte0emqv84pnj
Fix bug #162486, by having RemoteBranch properly initialize self._revision_id_to_revno_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 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