~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

Merge bzr.dev to resolve conflicts

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