~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

[merge] robert's knit-performance work

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 SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
146
 
 
147
 
    def do_with_branch(self, branch):
148
 
        """Return the heads-to-fetch for a Branch as two bencoded lists.
149
 
        
150
 
        See Branch.heads_to_fetch.
151
 
 
152
 
        New in 2.4.
153
 
        """
154
 
        must_fetch, if_present_fetch = branch.heads_to_fetch()
155
 
        return SuccessfulSmartServerResponse(
156
 
            (list(must_fetch), list(if_present_fetch)))
157
 
 
158
 
 
159
 
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
160
 
 
161
 
    def do_with_branch(self, branch):
162
 
        stacked_on_url = branch.get_stacked_on_url()
163
 
        return SuccessfulSmartServerResponse(('ok', stacked_on_url))
164
 
 
165
 
 
166
 
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
167
 
 
168
 
    def do_with_branch(self, branch):
169
 
        """Get the revision history for the branch.
170
 
 
171
 
        The revision list is returned as the body content,
172
 
        with each revision utf8 encoded and \x00 joined.
173
 
        """
174
 
        return SuccessfulSmartServerResponse(
175
 
            ('ok', ), ('\x00'.join(branch.revision_history())))
176
 
 
177
 
 
178
 
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
179
 
 
180
 
    def do_with_branch(self, branch):
181
 
        """Return branch.last_revision_info().
182
 
 
183
 
        The revno is encoded in decimal, the revision_id is encoded as utf8.
184
 
        """
185
 
        revno, last_revision = branch.last_revision_info()
186
 
        return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
187
 
 
188
 
 
189
 
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
190
 
    """Base class for handling common branch request logic for requests that
191
 
    update the branch tip.
192
 
    """
193
 
 
194
 
    def do_with_locked_branch(self, branch, *args):
195
 
        try:
196
 
            return self.do_tip_change_with_locked_branch(branch, *args)
197
 
        except errors.TipChangeRejected, e:
198
 
            msg = e.msg
199
 
            if isinstance(msg, unicode):
200
 
                msg = msg.encode('utf-8')
201
 
            return FailedSmartServerResponse(('TipChangeRejected', msg))
202
 
 
203
 
 
204
 
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
205
 
    """Set an option in the branch configuration."""
206
 
 
207
 
    def do_with_locked_branch(self, branch, value, name, section):
208
 
        if not section:
209
 
            section = None
210
 
        branch._get_config().set_option(value.decode('utf8'), name, section)
211
 
        return SuccessfulSmartServerResponse(())
212
 
 
213
 
 
214
 
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
215
 
    """Set an option in the branch configuration.
216
 
    
217
 
    New in 2.2.
218
 
    """
219
 
 
220
 
    def do_with_locked_branch(self, branch, value_dict, name, section):
221
 
        utf8_dict = bencode.bdecode(value_dict)
222
 
        value_dict = {}
223
 
        for key, value in utf8_dict.items():
224
 
            value_dict[key.decode('utf8')] = value.decode('utf8')
225
 
        if not section:
226
 
            section = None
227
 
        branch._get_config().set_option(value_dict, name, section)
228
 
        return SuccessfulSmartServerResponse(())
229
 
 
230
 
 
231
 
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
232
 
 
233
 
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
234
 
        if new_last_revision_id == 'null:':
235
 
            branch._set_revision_history([])
236
 
        else:
237
 
            if not branch.repository.has_revision(new_last_revision_id):
238
 
                return FailedSmartServerResponse(
239
 
                    ('NoSuchRevision', new_last_revision_id))
240
 
            branch._set_revision_history(branch._lefthand_history(
241
 
                new_last_revision_id, None, None))
242
 
        return SuccessfulSmartServerResponse(('ok',))
243
 
 
244
 
 
245
 
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
246
 
 
247
 
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
248
 
            allow_divergence, allow_overwrite_descendant):
249
 
        """Set the last revision of the branch.
250
 
 
251
 
        New in 1.6.
252
 
 
253
 
        :param new_last_revision_id: the revision ID to set as the last
254
 
            revision of the branch.
255
 
        :param allow_divergence: A flag.  If non-zero, change the revision ID
256
 
            even if the new_last_revision_id's ancestry has diverged from the
257
 
            current last revision.  If zero, a 'Diverged' error will be
258
 
            returned if new_last_revision_id is not a descendant of the current
259
 
            last revision.
260
 
        :param allow_overwrite_descendant:  A flag.  If zero and
261
 
            new_last_revision_id is not a descendant of the current last
262
 
            revision, then the last revision will not be changed.  If non-zero
263
 
            and there is no divergence, then the last revision is always
264
 
            changed.
265
 
 
266
 
        :returns: on success, a tuple of ('ok', revno, revision_id), where
267
 
            revno and revision_id are the new values of the current last
268
 
            revision info.  The revision_id might be different to the
269
 
            new_last_revision_id if allow_overwrite_descendant was not set.
270
 
        """
271
 
        do_not_overwrite_descendant = not allow_overwrite_descendant
272
 
        try:
273
 
            last_revno, last_rev = branch.last_revision_info()
274
 
            graph = branch.repository.get_graph()
275
 
            if not allow_divergence or do_not_overwrite_descendant:
276
 
                relation = branch._revision_relations(
277
 
                    last_rev, new_last_revision_id, graph)
278
 
                if relation == 'diverged' and not allow_divergence:
279
 
                    return FailedSmartServerResponse(('Diverged',))
280
 
                if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
281
 
                    return SuccessfulSmartServerResponse(
282
 
                        ('ok', last_revno, last_rev))
283
 
            new_revno = graph.find_distance_to_null(
284
 
                new_last_revision_id, [(last_rev, last_revno)])
285
 
            branch.set_last_revision_info(new_revno, new_last_revision_id)
286
 
        except errors.GhostRevisionsHaveNoRevno:
287
 
            return FailedSmartServerResponse(
288
 
                ('NoSuchRevision', new_last_revision_id))
289
 
        return SuccessfulSmartServerResponse(
290
 
            ('ok', new_revno, new_last_revision_id))
291
 
 
292
 
 
293
 
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
294
 
    """Branch.set_last_revision_info.  Sets the revno and the revision ID of
295
 
    the specified branch.
296
 
 
297
 
    New in bzrlib 1.4.
298
 
    """
299
 
 
300
 
    def do_tip_change_with_locked_branch(self, branch, new_revno,
301
 
            new_last_revision_id):
302
 
        try:
303
 
            branch.set_last_revision_info(int(new_revno), new_last_revision_id)
304
 
        except errors.NoSuchRevision:
305
 
            return FailedSmartServerResponse(
306
 
                ('NoSuchRevision', new_last_revision_id))
307
 
        return SuccessfulSmartServerResponse(('ok',))
308
 
 
309
 
 
310
 
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
311
 
    """Set the parent location for a branch.
312
 
    
313
 
    Takes a location to set, which must be utf8 encoded.
314
 
    """
315
 
 
316
 
    def do_with_locked_branch(self, branch, location):
317
 
        branch._set_parent_location(location)
318
 
        return SuccessfulSmartServerResponse(())
319
 
 
320
 
 
321
 
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
322
 
 
323
 
    def do_with_branch(self, branch, branch_token='', repo_token=''):
324
 
        if branch_token == '':
325
 
            branch_token = None
326
 
        if repo_token == '':
327
 
            repo_token = None
328
 
        try:
329
 
            repo_token = branch.repository.lock_write(
330
 
                token=repo_token).repository_token
331
 
            try:
332
 
                branch_token = branch.lock_write(
333
 
                    token=branch_token).branch_token
334
 
            finally:
335
 
                # this leaves the repository with 1 lock
336
 
                branch.repository.unlock()
337
 
        except errors.LockContention:
338
 
            return FailedSmartServerResponse(('LockContention',))
339
 
        except errors.TokenMismatch:
340
 
            return FailedSmartServerResponse(('TokenMismatch',))
341
 
        except errors.UnlockableTransport:
342
 
            return FailedSmartServerResponse(('UnlockableTransport',))
343
 
        except errors.LockFailed, e:
344
 
            return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
345
 
        if repo_token is None:
346
 
            repo_token = ''
347
 
        else:
348
 
            branch.repository.leave_lock_in_place()
349
 
        branch.leave_lock_in_place()
350
 
        branch.unlock()
351
 
        return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
352
 
 
353
 
 
354
 
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
355
 
 
356
 
    def do_with_branch(self, branch, branch_token, repo_token):
357
 
        try:
358
 
            branch.repository.lock_write(token=repo_token)
359
 
            try:
360
 
                branch.lock_write(token=branch_token)
361
 
            finally:
362
 
                branch.repository.unlock()
363
 
        except errors.TokenMismatch:
364
 
            return FailedSmartServerResponse(('TokenMismatch',))
365
 
        if repo_token:
366
 
            branch.repository.dont_leave_lock_in_place()
367
 
        branch.dont_leave_lock_in_place()
368
 
        branch.unlock()
369
 
        return SuccessfulSmartServerResponse(('ok',))
370