~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

Add a group cache to decompression, 5 times faster than knit at decompression when accessing everything in a group.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Server-side branch related request implmentations."""
18
 
 
19
 
 
20
 
from bzrlib import (
21
 
    bencode,
22
 
    errors,
23
 
    )
24
 
from bzrlib.bzrdir import BzrDir
25
 
from bzrlib.smart.request import (
26
 
    FailedSmartServerResponse,
27
 
    SmartServerRequest,
28
 
    SuccessfulSmartServerResponse,
29
 
    )
30
 
 
31
 
 
32
 
class SmartServerBranchRequest(SmartServerRequest):
33
 
    """Base class for handling common branch request logic.
34
 
    """
35
 
 
36
 
    def do(self, path, *args):
37
 
        """Execute a request for a branch at path.
38
 
 
39
 
        All Branch requests take a path to the branch as their first argument.
40
 
 
41
 
        If the branch is a branch reference, NotBranchError is raised.
42
 
 
43
 
        :param path: The path for the repository as received from the
44
 
            client.
45
 
        :return: A SmartServerResponse from self.do_with_branch().
46
 
        """
47
 
        transport = self.transport_from_client_path(path)
48
 
        bzrdir = BzrDir.open_from_transport(transport)
49
 
        if bzrdir.get_branch_reference() is not None:
50
 
            raise errors.NotBranchError(transport.base)
51
 
        branch = bzrdir.open_branch(ignore_fallbacks=True)
52
 
        return self.do_with_branch(branch, *args)
53
 
 
54
 
 
55
 
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
56
 
    """Base class for handling common branch request logic for requests that
57
 
    need a write lock.
58
 
    """
59
 
 
60
 
    def do_with_branch(self, branch, branch_token, repo_token, *args):
61
 
        """Execute a request for a branch.
62
 
 
63
 
        A write lock will be acquired with the given tokens for the branch and
64
 
        repository locks.  The lock will be released once the request is
65
 
        processed.  The physical lock state won't be changed.
66
 
        """
67
 
        # XXX: write a test for LockContention
68
 
        branch.repository.lock_write(token=repo_token)
69
 
        try:
70
 
            branch.lock_write(token=branch_token)
71
 
            try:
72
 
                return self.do_with_locked_branch(branch, *args)
73
 
            finally:
74
 
                branch.unlock()
75
 
        finally:
76
 
            branch.repository.unlock()
77
 
 
78
 
 
79
 
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
80
 
 
81
 
    def do_with_branch(self, branch):
82
 
        """Return the content of branch.conf
83
 
 
84
 
        The body is not utf8 decoded - its the literal bytestream from disk.
85
 
        """
86
 
        try:
87
 
            content = branch._transport.get_bytes('branch.conf')
88
 
        except errors.NoSuchFile:
89
 
            content = ''
90
 
        return SuccessfulSmartServerResponse( ('ok', ), content)
91
 
 
92
 
 
93
 
class SmartServerBranchGetParent(SmartServerBranchRequest):
94
 
 
95
 
    def do_with_branch(self, branch):
96
 
        """Return the parent of branch."""
97
 
        parent = branch._get_parent_location() or ''
98
 
        return SuccessfulSmartServerResponse((parent,))
99
 
 
100
 
 
101
 
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
102
 
 
103
 
    def do_with_branch(self, branch):
104
 
        """Return the _get_tags_bytes for a branch."""
105
 
        bytes = branch._get_tags_bytes()
106
 
        return SuccessfulSmartServerResponse((bytes,))
107
 
 
108
 
 
109
 
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
110
 
 
111
 
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
112
 
        SmartServerLockedBranchRequest.__init__(
113
 
            self, backing_transport, root_client_path, jail_root)
114
 
        self.locked = False
115
 
        
116
 
    def do_with_locked_branch(self, branch):
117
 
        """Call _set_tags_bytes for a branch.
118
 
 
119
 
        New in 1.18.
120
 
        """
121
 
        # We need to keep this branch locked until we get a body with the tags
122
 
        # bytes.
123
 
        self.branch = branch
124
 
        self.branch.lock_write()
125
 
        self.locked = True
126
 
 
127
 
    def do_body(self, bytes):
128
 
        self.branch._set_tags_bytes(bytes)
129
 
        return SuccessfulSmartServerResponse(())
130
 
 
131
 
    def do_end(self):
132
 
        # TODO: this request shouldn't have to do this housekeeping manually.
133
 
        # Some of this logic probably belongs in a base class.
134
 
        if not self.locked:
135
 
            # We never acquired the branch successfully in the first place, so
136
 
            # there's nothing more to do.
137
 
            return
138
 
        try:
139
 
            return SmartServerLockedBranchRequest.do_end(self)
140
 
        finally:
141
 
            # Only try unlocking if we locked successfully in the first place
142
 
            self.branch.unlock()
143
 
 
144
 
 
145
 
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
146
 
 
147
 
    def do_with_branch(self, branch):
148
 
        stacked_on_url = branch.get_stacked_on_url()
149
 
        return SuccessfulSmartServerResponse(('ok', stacked_on_url))
150
 
 
151
 
 
152
 
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
153
 
 
154
 
    def do_with_branch(self, branch):
155
 
        """Get the revision history for the branch.
156
 
 
157
 
        The revision list is returned as the body content,
158
 
        with each revision utf8 encoded and \x00 joined.
159
 
        """
160
 
        return SuccessfulSmartServerResponse(
161
 
            ('ok', ), ('\x00'.join(branch.revision_history())))
162
 
 
163
 
 
164
 
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
165
 
 
166
 
    def do_with_branch(self, branch):
167
 
        """Return branch.last_revision_info().
168
 
 
169
 
        The revno is encoded in decimal, the revision_id is encoded as utf8.
170
 
        """
171
 
        revno, last_revision = branch.last_revision_info()
172
 
        return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
173
 
 
174
 
 
175
 
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
176
 
    """Base class for handling common branch request logic for requests that
177
 
    update the branch tip.
178
 
    """
179
 
 
180
 
    def do_with_locked_branch(self, branch, *args):
181
 
        try:
182
 
            return self.do_tip_change_with_locked_branch(branch, *args)
183
 
        except errors.TipChangeRejected, e:
184
 
            msg = e.msg
185
 
            if isinstance(msg, unicode):
186
 
                msg = msg.encode('utf-8')
187
 
            return FailedSmartServerResponse(('TipChangeRejected', msg))
188
 
 
189
 
 
190
 
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
191
 
    """Set an option in the branch configuration."""
192
 
 
193
 
    def do_with_locked_branch(self, branch, value, name, section):
194
 
        if not section:
195
 
            section = None
196
 
        branch._get_config().set_option(value.decode('utf8'), name, section)
197
 
        return SuccessfulSmartServerResponse(())
198
 
 
199
 
 
200
 
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
201
 
    """Set an option in the branch configuration.
202
 
    
203
 
    New in 2.2.
204
 
    """
205
 
 
206
 
    def do_with_locked_branch(self, branch, value_dict, name, section):
207
 
        utf8_dict = bencode.bdecode(value_dict)
208
 
        value_dict = {}
209
 
        for key, value in utf8_dict.items():
210
 
            value_dict[key.decode('utf8')] = value.decode('utf8')
211
 
        if not section:
212
 
            section = None
213
 
        branch._get_config().set_option(value_dict, name, section)
214
 
        return SuccessfulSmartServerResponse(())
215
 
 
216
 
 
217
 
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
218
 
 
219
 
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
220
 
        if new_last_revision_id == 'null:':
221
 
            branch.set_revision_history([])
222
 
        else:
223
 
            if not branch.repository.has_revision(new_last_revision_id):
224
 
                return FailedSmartServerResponse(
225
 
                    ('NoSuchRevision', new_last_revision_id))
226
 
            branch.set_revision_history(branch._lefthand_history(
227
 
                new_last_revision_id, None, None))
228
 
        return SuccessfulSmartServerResponse(('ok',))
229
 
 
230
 
 
231
 
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
232
 
 
233
 
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
234
 
            allow_divergence, allow_overwrite_descendant):
235
 
        """Set the last revision of the branch.
236
 
 
237
 
        New in 1.6.
238
 
 
239
 
        :param new_last_revision_id: the revision ID to set as the last
240
 
            revision of the branch.
241
 
        :param allow_divergence: A flag.  If non-zero, change the revision ID
242
 
            even if the new_last_revision_id's ancestry has diverged from the
243
 
            current last revision.  If zero, a 'Diverged' error will be
244
 
            returned if new_last_revision_id is not a descendant of the current
245
 
            last revision.
246
 
        :param allow_overwrite_descendant:  A flag.  If zero and
247
 
            new_last_revision_id is not a descendant of the current last
248
 
            revision, then the last revision will not be changed.  If non-zero
249
 
            and there is no divergence, then the last revision is always
250
 
            changed.
251
 
 
252
 
        :returns: on success, a tuple of ('ok', revno, revision_id), where
253
 
            revno and revision_id are the new values of the current last
254
 
            revision info.  The revision_id might be different to the
255
 
            new_last_revision_id if allow_overwrite_descendant was not set.
256
 
        """
257
 
        do_not_overwrite_descendant = not allow_overwrite_descendant
258
 
        try:
259
 
            last_revno, last_rev = branch.last_revision_info()
260
 
            graph = branch.repository.get_graph()
261
 
            if not allow_divergence or do_not_overwrite_descendant:
262
 
                relation = branch._revision_relations(
263
 
                    last_rev, new_last_revision_id, graph)
264
 
                if relation == 'diverged' and not allow_divergence:
265
 
                    return FailedSmartServerResponse(('Diverged',))
266
 
                if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
267
 
                    return SuccessfulSmartServerResponse(
268
 
                        ('ok', last_revno, last_rev))
269
 
            new_revno = graph.find_distance_to_null(
270
 
                new_last_revision_id, [(last_rev, last_revno)])
271
 
            branch.set_last_revision_info(new_revno, new_last_revision_id)
272
 
        except errors.GhostRevisionsHaveNoRevno:
273
 
            return FailedSmartServerResponse(
274
 
                ('NoSuchRevision', new_last_revision_id))
275
 
        return SuccessfulSmartServerResponse(
276
 
            ('ok', new_revno, new_last_revision_id))
277
 
 
278
 
 
279
 
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
280
 
    """Branch.set_last_revision_info.  Sets the revno and the revision ID of
281
 
    the specified branch.
282
 
 
283
 
    New in bzrlib 1.4.
284
 
    """
285
 
 
286
 
    def do_tip_change_with_locked_branch(self, branch, new_revno,
287
 
            new_last_revision_id):
288
 
        try:
289
 
            branch.set_last_revision_info(int(new_revno), new_last_revision_id)
290
 
        except errors.NoSuchRevision:
291
 
            return FailedSmartServerResponse(
292
 
                ('NoSuchRevision', new_last_revision_id))
293
 
        return SuccessfulSmartServerResponse(('ok',))
294
 
 
295
 
 
296
 
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
297
 
    """Set the parent location for a branch.
298
 
    
299
 
    Takes a location to set, which must be utf8 encoded.
300
 
    """
301
 
 
302
 
    def do_with_locked_branch(self, branch, location):
303
 
        branch._set_parent_location(location)
304
 
        return SuccessfulSmartServerResponse(())
305
 
 
306
 
 
307
 
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
308
 
 
309
 
    def do_with_branch(self, branch, branch_token='', repo_token=''):
310
 
        if branch_token == '':
311
 
            branch_token = None
312
 
        if repo_token == '':
313
 
            repo_token = None
314
 
        try:
315
 
            repo_token = branch.repository.lock_write(
316
 
                token=repo_token).repository_token
317
 
            try:
318
 
                branch_token = branch.lock_write(
319
 
                    token=branch_token).branch_token
320
 
            finally:
321
 
                # this leaves the repository with 1 lock
322
 
                branch.repository.unlock()
323
 
        except errors.LockContention:
324
 
            return FailedSmartServerResponse(('LockContention',))
325
 
        except errors.TokenMismatch:
326
 
            return FailedSmartServerResponse(('TokenMismatch',))
327
 
        except errors.UnlockableTransport:
328
 
            return FailedSmartServerResponse(('UnlockableTransport',))
329
 
        except errors.LockFailed, e:
330
 
            return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
331
 
        if repo_token is None:
332
 
            repo_token = ''
333
 
        else:
334
 
            branch.repository.leave_lock_in_place()
335
 
        branch.leave_lock_in_place()
336
 
        branch.unlock()
337
 
        return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
338
 
 
339
 
 
340
 
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
341
 
 
342
 
    def do_with_branch(self, branch, branch_token, repo_token):
343
 
        try:
344
 
            branch.repository.lock_write(token=repo_token)
345
 
            try:
346
 
                branch.lock_write(token=branch_token)
347
 
            finally:
348
 
                branch.repository.unlock()
349
 
        except errors.TokenMismatch:
350
 
            return FailedSmartServerResponse(('TokenMismatch',))
351
 
        if repo_token:
352
 
            branch.repository.dont_leave_lock_in_place()
353
 
        branch.dont_leave_lock_in_place()
354
 
        branch.unlock()
355
 
        return SuccessfulSmartServerResponse(('ok',))
356