~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Aaron Bentley
  • Date: 2007-06-20 22:06:22 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070620220622-9lasxr96rr0e0xvn
Use a fresh versionedfile each time

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