~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

  • Committer: Martin Pool
  • Date: 2010-09-03 09:14:12 UTC
  • mto: This revision was merged to the branch mainline in revision 5417.
  • Revision ID: mbp@sourcefrog.net-20100903091412-1a40klgfg8c6k3xj
Split out user interaction developer guide to a separate file

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