~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

Fix commit message template for non-ascii files, and add test for handling of
non-unicode.

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